<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/hierarchical-compartmental-reserving-models/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/"> R on mages' blog</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Today, I will sketch out ideas from the <a rel="nofollow" href="https://www.casact.org/pubs/forum/16sforum/Morris.pdf" target="_blank"><em>Hierarchical Compartmental Models for Loss Reserving</em></a> paper by Jake Morris, which was published in the summer of 2016 (<span>Morris (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-JakeMorris2016" target="_blank">2016</a>)</span>). Jake’s model is inspired by PK/PD models (pharmacokinetic/pharmacodynamic models) used in the pharmaceutical industry to describe the time course of effect intensity in response to administration of a drug dose.</p>
<p>The hierarchical compartmental model fits outstanding and paid claims simultaneously, combining ideas of <span>Clark (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-Clark2003" target="_blank">2003</a>)</span>, <span>Quarg and Mack (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-Quarg2004" target="_blank">2004</a>)</span>, <span>Miranda, Nielsen, and Verrall (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-miranda2012double" target="_blank">2012</a>)</span>, <span>Guszcza (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-Guszcza2008" target="_blank">2008</a>)</span> and <span>Zhang, Dukic, and Guszcza (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-zhang2012bayesian" target="_blank">2012</a>)</span>. You find the first two models implemented in the <a rel="nofollow" href="https://cran.r-project.org/package=DCL" target="_blank"><code>ChainLadder</code></a> package, the third in the <a rel="nofollow" href="https://cran.r-project.org/package=DCL" target="_blank"><code>DCL</code></a> package and the fourth one in an earlier <a rel="nofollow" href="https://magesblog.com/post/2015-11-10-hierarchical-loss-reserving-with-stan/" target="_blank">blog post</a> of mine and as part of the <code>brms</code> package <a rel="nofollow" href="https://cran.r-project.org/web/packages/brms/vignettes/brms_nonlinear.html#a-real-world-non-linear-model" target="_blank">vignette</a>.</p>
<p>Jake uses <a rel="nofollow" href="https://www.mrc-bsu.cam.ac.uk/software/bugs/" target="_blank">OpenBUGS</a> (<span>Lunn et al. (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-lunn2000winbugs" target="_blank">2000</a>)</span>) for the Bayesian examples in his paper. Thanks to Paul-Christian Bürkner’s <code>brms</code> package (<span>Bürkner (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-brms" target="_blank">2017</a>)</span>) I can run version of Jake’s model in Stan (<span>Stan Development Team (<a rel="nofollow" href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/#ref-rstan" target="_blank">2017</a>)</span>) easily, as I will show in this post.</p>
<p><em>Note:</em> Paul and Jake will be at the Stan workshop in London, following the <a rel="nofollow" href="https://insurancedatascience.org/" target="_blank">Insurance Data Science conference</a>, on 17 July 2018.</p>
<div>
<h1>Hierarchical Compartmental Models</h1>
<p>Similar to a PK/PD model, Jake describes the state-space process of claims developments with a set of ordinary differential equations (ODE), using <span>\(EX=\)</span> Exposure, <span>\(OS=\)</span> Outstanding claims, <span>\(PD=\)</span> Paid claims (these are the compartments):</p>
<p><span>\[<br>
\begin{aligned}<br>
dEX/dt &amp; = -k_{er} \cdot EX \\<br>
dOS/dt &amp; = k_{er} \cdot RLR \cdot EX – k_p \cdot OS \\<br>
dPD/dt &amp; = k_{p} \cdot RRF \cdot OS<br>
\end{aligned}<br>
\]</span></p>
<p>with initial conditions <span>\(EX(0) = \Pi\)</span> (ultimate earned premiums), <span>\(OS(0)=0, PD(0)=0\)</span>.</p>
<p>The parameters describe:</p>
<ul>
<li><span>\(k_{er}\)</span> the rate at which claim events occur and are subsequently reported to the insurer</li>
<li><span>\(RLR\)</span> the reported loss ratio</li>
<li><span>\(RRF\)</span> the reserve robustness factor, the proportion of outstanding claims that eventually are paid</li>
<li><span>\(k_p\)</span> the rate of payment, i.e. the rate at which outstanding claims are paid</li>
</ul>
<p>Note, the product of <span>\(RLR\)</span> and <span>\(RRF\)</span> describes the ultimate loss ratio <span>\(ULR =\)</span> ultimate loss / premium, which is a key metric in insurance to assess performance and profitability.</p>
<p>Setting the parameters <span>\(k_{er}=1.7\)</span>, <span>\(RLR=0.8\)</span>, <span>\(k_p=0.5\)</span>, <span>\(RRF=0.95\)</span> produces the following output.</p>
<p><img src="https://i1.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/odeSolve-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>The chart illustrates nicely the different compartments (processes) for a policy over time.</p>
<p>The autonomous system of ODEs above can be solved analytically:</p>
<p><span>\[<br>
\begin{aligned}<br>
EX(t) &amp; = \Pi \cdot \exp(- k_{er} t) \\<br>
OS(t) &amp; = \frac{\Pi \cdot RLR \cdot k_{er}}{k_{er} – k_p} \cdot<br>
\left(\exp(-k_p t) – \exp(-k_{er} t)\right)\\<br>
PD (t) &amp; = \frac{\Pi \cdot RLR \cdot RRF}{k_{er} – k_p}<br>
\left( k_{er} \cdot (1 – \exp(-k_p t) – k_p \cdot (1 – \exp(-k_{er}t ) \right)<br>
\end{aligned}<br>
\]</span></p>
<p>In this post I will focus on the last two equations, as they describe the observable data of outstanding claims and cumulative paid claims over time. However, one could expand the model, e.g. to allow for different premium earning patterns.</p>
</div>
<div>
<h1>Get example data</h1>
<p>In his paper Jake uses data from the <a rel="nofollow" href="http://www.casact.org/research/index.cfm?fa=loss_reserves_data" target="_blank">CAS Reserving Database</a>, namely company 337 from the worker’s compensation file.</p>
<p>The following function downloads the data and reshapes it into a more useful format, very similar to the function used in an <a rel="nofollow" href="https://magesblog.com/post/correlated-lognormal-chain-ladder-model/" target="_blank">earlier post</a> for Commercial Auto data.</p>
<pre><code>library(data.table)
CASdata &lt;- fread(&quot;http://www.casact.org/research/reserve_data/wkcomp_pos.csv&quot;)
createLossData2 &lt;- function(CASdata, company_code){
  compData &lt;- CASdata[GRCODE==company_code,
                      c(&quot;EarnedPremDIR_D&quot;, &quot;AccidentYear&quot;, &quot;DevelopmentLag&quot;, 
                        &quot;IncurLoss_D&quot;, &quot;CumPaidLoss_D&quot;, &quot;BulkLoss_D&quot;)]
  setnames(compData, names(compData),
           c(&quot;premium&quot;, &quot;accident_year&quot;, &quot;dev&quot;,
             &quot;incurred_loss&quot;, &quot;paid_loss&quot;, &quot;bulk_loss&quot;))
  compData &lt;- compData[, `:=`(
    origin = accident_year - min(accident_year) + 1)]
  compData0 &lt;- compData[dev==1]
  compData0 &lt;- compData0[, `:=`(dev = 0, incurred_loss = 0,
                                paid_loss = 0, bulk_loss = 0)]
  compData &lt;- rbindlist(list(compData0, compData))
  compData &lt;- compData[, cal := origin + dev - 1][order(origin, dev)]
  compData &lt;- compData[, `:=`(
    paid_train = ifelse(cal &lt;= max(origin), paid_loss, NA),
    paid_test = ifelse(cal > max(origin), paid_loss, NA),
    os_train = ifelse(cal &lt;= max(origin), incurred_loss - paid_loss, NA),
    os_test = ifelse(cal > max(origin), incurred_loss - paid_loss, NA))]
  traintest &lt;- rbindlist(list(compData[cal &lt;= max(origin)],
                              compData[cal > max(origin)]))
  return(traintest)
}
lossData &lt;- createLossData2(CASdata, company_code = 337)</code></pre>
<p>Let’s plot the data of outstanding and paid loss ratio against development years (<span>\(t\)</span>=<code>dev</code>) for the different accident years.</p>
<p><img src="https://i0.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/devPlot-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>The data looks similar to the example output from our model above. That’s a good start.</p>
<div>
<h2>Re-organising data</h2>
<p>In order to fit the two curves simultaneously I use a little trick. I stack the paid and outstanding claims into a single column and add another indicator column (<code>delta</code>), which is either <span>\(0\)</span> for outstanding claims or <span>\(1\)</span> for paid claims. This turns the multivariate data back into a univariate set.</p>
<p>Additionally, I remove the artificial data for <code>dev=0</code> again, add another column of the indicator variable as a factor (<code>deltaf</code>) and divide all measurement variables by the premium of the oldest accident year. The last step normalises the data and should make modelling a little easier as everything is in the ballpark of 1.</p>
<pre><code>lossData0 &lt;- 
 rbindlist(
   list(lossData[, list(accident_year, dev, loss_train=os_train, 
                        loss_test=os_test, delta=0, premium=premium)],
        lossData[,list(accident_year, dev,loss_train=paid_train, 
                       loss_test=paid_test, delta=1, premium=premium)]
   ))[order(accident_year, dev)]

premind &lt;- lossData0[accident_year==min(accident_year) &amp; dev==min(dev) &amp; delta==1, premium]
lossData0 &lt;- lossData0[, `:=`(premium = premium/premind,
                              loss_train = loss_train/premind,
                              loss_test = loss_test/premind,
                              deltaf = factor(delta, labels = c(&quot;os&quot;, &quot;paid&quot;)),
                              cal=accident_year + dev - 1)][dev>0]</code></pre>
</div>
</div>
<div>
<h1>Model fitting</h1>
<div>
<h2>Non-linear Least Squares</h2>
<p>Before I build a complex Bayesian model I start with a simple non-linear least squares model. Or in other words, I believe the data is generated from a Normal distribution, with the mean described by an analytical function <span>\(\mu(t)=f(t,\dots)\)</span> and constant variance <span>\(\sigma^2\)</span>.</p>
<p><span>\[<br>
\begin{aligned}<br>
y(t) &amp; \sim \mathcal{N}(\mu(t), \sigma^2) \\<br>
\mu(t) &amp; = f(t, \Pi, k_{er}, k_p, RLR, RRF, \delta)\\<br>
&amp; = \Pi \cdot [(1 – \delta) \frac{ RLR \cdot k_{er}}{k_{er} – k_p} \cdot<br>
\left(\exp(-k_p t) – \exp(-k_{er} t) \right) + \\<br>
&amp; \delta \frac{ RLR \cdot RRF}{k_{er} – k_p}<br>
\left( k_{er} \cdot (1 – \exp(-k_p t) – k_p \cdot (1 – \exp(-k_{er}t ) \right)]\\<br>
\delta &amp; = \begin{cases}<br>
1 \mbox{ if } y \mbox{ is outstanding claim}\\<br>
0 \mbox{ if } y\mbox{ is paid claim}<br>
\end{cases}<br>
\end{aligned}<br>
\]</span></p>
<p>To ensure all parameters stay positive I will use the same approach as Jake does in this paper, that is I reparameterize using the logarithm of the parameters.</p>
<pre><code>my.f &lt;- function(t, premium, lk_er,  lk_p, lRLR, lRRF, delta){
  k_er &lt;- exp(lk_er)  
  k_p &lt;- exp(lk_p) 
  RLR &lt;- exp(lRLR) 
  RRF &lt;- exp(lRRF)
  paid &lt;- (RLR * k_er / (k_er - k_p) * (exp(-k_p * t) - exp(-k_er * t)))
  os &lt;-  (RLR * RRF / (k_er - k_p) * (k_er * (1 - exp(-k_p * t)) - 
                                        k_p * (1 - exp(-k_er * t))))
  return(premium * (paid * (1 - delta) + os * delta))
}</code></pre>
<p>Using the <code>nls</code> function in R the model can be fitted quickly.</p>
<pre><code>n1 &lt;- nls(loss_train ~ my.f(dev, premium,
                            lk_er=lker, lk_p=lkp, 
                            lRLR=lRLR, lRRF=lRRF, delta=delta),
          data=lossData0[cal&lt;=max(accident_year)],
          start = c(lker = log(1.5), lRLR = log(1),
                    lkp = log(0.75), lRRF = log(0.75)))
n1</code></pre>
<pre><code>## Nonlinear regression model
##   model: loss_train ~ my.f(dev, premium, lk_er = lker, lk_p = lkp, lRLR = lRLR,     lRRF = lRRF, delta = delta)
##    data: lossData0[cal &lt;= max(accident_year)]
##    lker    lRLR     lkp    lRRF 
##  0.8621 -0.1090 -0.8646 -0.4397 
##  residual sum-of-squares: 0.3505
## 
## Number of iterations to convergence: 5 
## Achieved convergence tolerance: 2.844e-06</code></pre>
<p>Let’s bring the coefficients back to the original scale:</p>
<pre><code>n1par &lt;- data.table(summary(n1)$coefficients)[, exp(Estimate + 0.5*`Std. Error`^2)]
names(n1par) &lt;- c(&quot;ker&quot;, &quot;RLR&quot;, &quot;kp&quot;, &quot;RRF&quot;)
n1par</code></pre>
<pre><code>##       ker       RLR        kp       RRF 
## 2.4375609 0.8985600 0.4231800 0.6466241</code></pre>
<p>Assuming a constant variance across outstanding (<code>delta=0</code>) and paid claims (<code>delta=1</code>) doesn’t really make much sense, neither that the parameters <code>RLR</code> and <code>RRF</code> are the same across all accident years, which assumes the same ULR for all years: 57.8%.</p>
<p><img src="https://i1.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-4-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p><img src="https://i1.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-5-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
</div>
<div>
<h2>Hierarchical non-linear model</h2>
<p>To address the issues mentioned above, I allow for different variances depending on the compartment type (<span>\(\sigma^2_{y[\delta]}\)</span>) and allow the parameters RLR and RRF to vary across accident years.</p>
<p><span>\[<br>
\begin{aligned}<br>
y(t) &amp; \sim \mathcal{N}(f(t, \Pi, k_{er}, k_p, RLR_{[i]}, RRF_{[i]}), \sigma_{y[\delta]}^2) \\<br>
\begin{pmatrix} RLR_{[i]} \\ RRF_{[i]}\end{pmatrix} &amp; \sim<br>
\mathcal{N} \left(<br>
\begin{pmatrix}<br>
\mu_{RLR} \\<br>
\mu_{RRF}<br>
\end{pmatrix},<br>
\begin{pmatrix}<br>
\sigma_{RLR}^2 &amp; 0 \\<br>
0 &amp; \sigma_{RRF}^2<br>
\end{pmatrix}<br>
\right)<br>
\end{aligned}<br>
\]</span></p>
<p>The parameters <span>\(\mu_{RLR}, \mu_{RRF}\)</span> describe the underlying means across all years <span>\(i\)</span>.</p>
<p>With the preparation done, I can fit the hierarchical non-linear model using the <code>nlme</code> package, assuming that the parameters <code>lRLR</code> and <code>lRRF</code> vary across accident years (random effects), while I treat the parameters <code>lker</code> and <code>lkp</code> as fixed effects.</p>
<pre><code>library(nlme)
m1 &lt;- nlme(loss_train ~ my.f(dev, premium,
                             lk_er=lker, lk_p=lkp, 
                             lRLR=lRLR, lRRF=lRRF, delta=delta),
           data=lossData0[cal&lt;=max(accident_year)],
           fixed = lker + lRLR + lkp + lRRF ~ 1,
           random = pdDiag(lRLR + lRRF ~ 1),
           groups = ~ accident_year,
           weights = varIdent(form = ~ 1 | deltaf),
           start = c(lker = log(1.5), lRLR = log(1),
                     lkp = log(0.75), lRRF = log(0.75)),
           control=list(msMaxIter=10000, pnlsMaxIter=10000,
                        pnlsTol=0.4)) </code></pre>
<pre><code>summary(m1)</code></pre>
<pre><code>## Nonlinear mixed-effects model fit by maximum likelihood
##   Model: loss_train ~ my.f(dev, premium, lk_er = lker, lk_p = lkp, lRLR = lRLR,      lRRF = lRRF, delta = delta) 
##  Data: lossData0[cal &lt;= max(accident_year)] 
##         AIC       BIC   logLik
##   -524.4347 -502.8309 270.2174
## 
## Random effects:
##  Formula: list(lRLR ~ 1, lRRF ~ 1)
##  Level: accident_year
##  Structure: Diagonal
##             lRLR      lRRF   Residual
## StdDev: 0.186949 0.1318405 0.03337559
## 
## Variance function:
##  Structure: Different standard deviations per stratum
##  Formula: ~1 | deltaf 
##  Parameter estimates:
##        os      paid 
## 1.0000000 0.1805809 
## Fixed effects: lker + lRLR + lkp + lRRF ~ 1 
##           Value  Std.Error DF    t-value p-value
## lker  0.4102733 0.05624434 97   7.294481  0.0000
## lRLR  0.0225969 0.06751438 97   0.334698  0.7386
## lkp  -0.7946096 0.03483365 97 -22.811551  0.0000
## lRRF -0.4049580 0.05558069 97  -7.285948  0.0000
##  Correlation: 
##      lker   lRLR   lkp   
## lRLR -0.375              
## lkp  -0.928  0.388       
## lRRF  0.522 -0.282 -0.572
## 
## Standardized Within-Group Residuals:
##         Min          Q1         Med          Q3         Max 
## -2.63652139 -0.52065601  0.03990089  0.61348869  2.01628548 
## 
## Number of Observations: 110
## Number of Groups: 10</code></pre>
<p>The parameters <span>\(k_{er}\)</span>, <span>\(\mu_{RLR}\)</span>, <span>\(k_p\)</span>, <span>\(\mu_{RRF}\)</span> on the original scale are:</p>
<pre><code>m1_fe &lt;- data.table(summary(m1)$tTable)[, exp(Value + 0.5*`Std.Error`^2)]
names(m1_fe) &lt;- c(&quot;ker&quot;, &quot;RLR&quot;, &quot;kp&quot;, &quot;RRF&quot;)
m1_fe</code></pre>
<pre><code>##       ker       RLR        kp       RRF 
## 1.5096155 1.0251880 0.4520317 0.6680359</code></pre>
<p>If you look at the p-Value of <code>lRLR</code>, we might as well assume <span>\(\mu_{RLR}=1\)</span>.</p>
<p>The estimated ULR across accident years is 68.5%, and the median by accident year:</p>
<pre><code>RLRRRF &lt;- coef(m1)[,c(2,4)]
names(RLRRRF) &lt;- c(&quot;RLR&quot;, &quot;RRF&quot;)
round(exp(cbind(RLRRRF, ULR=apply(RLRRRF,1,sum))),3)</code></pre>
<pre><code>##        RLR   RRF   ULR
## 1988 0.853 0.593 0.506
## 1989 0.925 0.577 0.534
## 1990 0.968 0.673 0.651
## 1991 0.910 0.798 0.727
## 1992 0.946 0.663 0.627
## 1993 0.899 0.562 0.505
## 1994 0.885 0.611 0.540
## 1995 1.232 0.724 0.892
## 1996 1.406 0.785 1.104
## 1997 1.382 0.734 1.014</code></pre>
<p>The parameters <span>\(\sigma_{y[\delta]}\)</span> are:</p>
<pre><code>(sig_delta &lt;- sapply(split(resid(m1), lossData0[cal&lt;=max(accident_year)]$deltaf), sd))</code></pre>
<pre><code>##          os        paid 
## 0.032065014 0.005522208</code></pre>
<p>and <span>\(\sigma_{RLR}\)</span> and <span>\(\sigma_{RRF}\)</span> are:</p>
<pre><code>VarCorr(m1)</code></pre>
<pre><code>## accident_year = pdDiag(list(lRLR ~ 1,lRRF ~ 1)) 
##          Variance   StdDev    
## lRLR     0.03494992 0.18694896
## lRRF     0.01738192 0.13184052
## Residual 0.00111393 0.03337559</code></pre>
<p>Let’s looks at the predictions:</p>
<p><img src="https://i1.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-14-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p><img src="https://i1.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-15-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>Looks good. This is a huge improvement to the simple non-linear least squares model. For all years but 1997 (here we had only one data point) the predictions look pretty reasonable. The more data we have the more credibility will be given to it and shift parameters RLR and RRF away from the population mean.</p>
</div>
<div>
<h2>Bayesian reserving models with Stan</h2>
<p>We can fit the same model with Stan, which allows us to make different assumptions on the data generating distribution and specify the priors more flexibly. For example, I’d like to assume Gamma priors over my parameters, this will ensure the parameters are all positive and I don’t have to reparameterise them as I did above.</p>
<pre><code>curve(dgamma(x, 4, 5), from = 0, 5, main=&quot;Gamma priors&quot;,
      xlab=&quot;&quot;, ylab=&quot;&quot;, bty=&quot;n&quot;)
curve(dgamma(x, 3, 2), add = TRUE, lty=3)
curve(dgamma(x, 3, 4), add=TRUE, lty=5)
text(x = c(0.1, 1.1, 2), y = c(0.8, 0.9, 0.4), labels = c(&quot;k_p&quot;, &quot;RLR,\nRRF&quot;, &quot;k_er&quot;))</code></pre>
<p><img src="https://i0.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/priors-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>Next I specify my model with <code>brm</code>, which will not only generate the Stan code, but also run the model and collect all the results. I continue to use a Gaussian distribution, as it will allow me to compare the output of <code>brm</code> with <code>nlme</code>.</p>
<pre><code>library(rstan)
library(brms)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

fml &lt;- 
  loss_train ~ premium * (
    (RLR*ker/(ker - kp) * (exp(-kp*dev) - exp(-ker*dev))) * (1 - delta) + 
      (RLR*RRF/(ker - kp) * (ker *(1 - exp(-kp*dev)) - kp*(1 - exp(-ker*dev)))) * delta
    ) 
b1 &lt;- brm(bf(fml,
             RLR ~ 1 + (1 | accident_year),
             RRF ~ 1 + (1 | accident_year),
             ker ~ 1,
             kp ~ 1,
             sigma ~ 0 + deltaf,
             nl = TRUE),
          data = lossData0[cal &lt;= max(accident_year)], 
          family = brmsfamily(&quot;gaussian&quot;, link_sigma = &quot;log&quot;),
          prior = c(prior(gamma(4, 5), nlpar = &quot;RLR&quot;, lb=0),
                    prior(gamma(4, 5), nlpar = &quot;RRF&quot;, lb=0),
                    prior(gamma(3, 2), nlpar = &quot;ker&quot;, lb=0),
                    prior(gamma(3, 4), nlpar = &quot;kp&quot;, lb=0)),
          control = list(adapt_delta = 0.999, max_treedepth=15),
          seed = 1234, iter = 1000)</code></pre>
<pre><code>b1</code></pre>
<pre><code>##  Family: gaussian 
##   Links: mu = identity; sigma = log 
## Formula: loss_train ~ premium * ((RLR * ker/(ker - kp) * (exp(-kp * dev) - exp(-ker * dev))) * (1 - delta) + (RLR * RRF/(ker - kp) * (ker * (1 - exp(-kp * dev)) - kp * (1 - exp(-ker * dev)))) * delta) 
##          RLR ~ 1 + (1 | accident_year)
##          RRF ~ 1 + (1 | accident_year)
##          ker ~ 1
##          kp ~ 1
##          sigma ~ 0 + deltaf
##    Data: lossData0[cal &lt;= max(accident_year)] (Number of observations: 110) 
## Samples: 4 chains, each with iter = 1000; warmup = 500; thin = 1; 
##          total post-warmup samples = 2000
##     ICs: LOO = NA; WAIC = NA; R2 = NA
##  
## Group-Level Effects: 
## ~accident_year (Number of levels: 10) 
##                   Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## sd(RLR_Intercept)     0.25      0.08     0.14     0.47        709 1.00
## sd(RRF_Intercept)     0.11      0.04     0.06     0.20        838 1.01
## 
## Population-Level Effects: 
##                  Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## RLR_Intercept        1.03      0.09     0.85     1.20        602 1.01
## RRF_Intercept        0.67      0.05     0.58     0.76        701 1.00
## ker_Intercept        1.55      0.11     1.37     1.77       1111 1.00
## kp_Intercept         0.45      0.02     0.42     0.49       1181 1.00
## sigma_deltafos      -3.38      0.12    -3.60    -3.12       2000 1.00
## sigma_deltafpaid    -5.07      0.13    -5.31    -4.79       1326 1.00
## 
## Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample 
## is a crude measure of effective sample size, and Rhat is the potential 
## scale reduction factor on split chains (at convergence, Rhat = 1).</code></pre>
<p>This looks similar to the output of <code>nlme</code>. Note, the link function for <code>sigma</code> was log, so to compare the output with <code>nlme</code> I have to transform it back to the original scale:</p>
<pre><code>library(rstan)
X &lt;- extract(b1$fit)
(sig_delta_brm &lt;- apply(X$b_sigma, 2, function(x) exp(mean(x)+0.5*var(x))))</code></pre>
<pre><code>## [1] 0.034170216 0.006316905</code></pre>
<p>Again, that’s pretty similar.</p>
<p>The simulations are well behaved as well:</p>
<pre><code>plot(b1, N = 4, ask = FALSE)</code></pre>
<p><img src="https://i1.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-18-1.png?w=450&ssl=1" data-recalc-dims="1"><img src="https://i0.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-18-2.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>Next I want to compare the estimated accident year ULRs with the ‘population’ level ULR.</p>
<pre><code>RLR &lt;- sweep(X$r_1_RLR_1, 1, X$b_RLR, &quot;+&quot;)
RRF &lt;- sweep(X$r_2_RRF_1, 1, X$b_RRF, &quot;+&quot;)
ULR &lt;- t(apply(RLR * RRF, 2, quantile, c(0.025, 0.5, 0.975)))
matplot(unique(lossData0$accident_year), ULR*100, 
        t=&quot;l&quot;, ylim=c(0, 150), lty=1, col=c(1,2,4), bty=&quot;n&quot;, 
        ylab=&quot;Projected ULR (%)&quot;, xlab=&quot;Accident year&quot;)
legend(&quot;topleft&quot;, legend = c(&quot;2.5%ile&quot;,&quot;50%ile&quot;, &quot;97.5%ile&quot;),
       lty=1, col=c(1,2,4), bty=&quot;n&quot;)
baseULR &lt;- X$b_RLR * X$b_RRF
abline(h=quantile(baseULR, 0.025)*100, col=1, lty=3)
abline(h=median(baseULR)*100, col=2, lty=3)
abline(h=quantile(baseULR, 0.975)*100, col=4, lty=3)</code></pre>
<p><img src="https://i2.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-19-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>That’s interesting, as the softening casualty market (increasing loss ratio) of the late 1990’s is very visible. From this plot you would assume 1996 was the worst year and perhaps an outlier. However, as the data shows, 1997 was even less profitable than 1996.</p>
<p>Perhaps, the under-prediction for 1997 may arises because historically RRF&lt;1 i.e. over-reserving. However, this switches to under-reserving in more recent accident years as the reserving cycle kicks in (for the latest accident year in particular). With only 2 data points from which to assess this, shrinkage drags the RRF parameter downwards. It would be an interesting exercise to try and incorporate a prior which captures the expectation of a deteriotating reserving cycle.</p>
<p>Let’s plot the predictions of the model, with the <code>plotDevBananas</code> function from an <a rel="nofollow" href="https://magesblog.com/post/correlated-lognormal-chain-ladder-model/" target="_blank">earlier post</a>.</p>
<pre><code>predClaimsPred &lt;- predict(b1, newdata = lossData0, method=&quot;predict&quot;)
plotDevBananas(`2.5%ile`/premium*100 + `97.5%ile`/premium*100 + 
                 Estimate/premium*100 + loss_train/premium*100 + 
                 loss_test/premium*100 ~ 
                 dev | factor(accident_year), ylim=c(0, 100),
               data=cbind(lossData0, predClaimsPred)[delta==0], 
               main=&quot;Outstanding claims developments&quot;, 
               ylab=&quot;Outstanding loss ratio (%)&quot;)</code></pre>
<p><img src="https://i0.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-21-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<pre><code>plotDevBananas(`2.5%ile`/premium*100 + `97.5%ile`/premium*100 + 
                 Estimate/premium*100 + loss_train/premium*100 + 
                 loss_test/premium*100 ~  
                 dev | factor(accident_year), ylim=c(0, 120),
               data=cbind(lossData0, predClaimsPred)[delta==1], 
               main=&quot;Paid claims developments&quot;, 
               ylab=&quot;Paid loss ratio (%)&quot;)</code></pre>
<p><img src="https://i0.wp.com/magesblog.com/post/2018-01-09-hierarchical-compartmental-reserving-models_files/figure-html/unnamed-chunk-22-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>The output looks very promising, apart form the 1997 accident year, which turned out even worse than 1996. Perhaps, I shouldn’t assume a Gaussian data generating distribution, but a distribution which is skewed to the right, such as Gamma, Weibull or Log-normal, all of which is doable with <code>brm</code> by changing the <code>family</code> argument.</p>
<p>Additionally, I could test for auto-correlation, calendar year effects, etc. Jake’s paper has further details on this, he also shows how the model can be extended to cover time-dependent parameters and covariate sub-models.</p>
<p>If you’d like to learn more about <code>brms</code> and compartmental reserving model attend the Stan workshop at Cass Business School London, following the <a rel="nofollow" href="https://insurancedatascience.org/" target="_blank">Insurance Data Science conference</a>, on 17 July 2018, where Paul and Jake will be there as well.</p>
<p>
</div>
</div>
<div>
<h1>Session Info</h1>
<pre><code>sessionInfo()</code></pre>
<pre><code>## R version 3.4.3 (2017-11-30)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS High Sierra 10.13.2
## 
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8
## 
## attached base packages:
## [1] methods   stats     graphics  grDevices utils     datasets  base     
## 
## other attached packages:
## [1] rstan_2.17.2        StanHeaders_2.17.1  ggplot2_2.2.1      
## [4] nlme_3.1-131        latticeExtra_0.6-28 RColorBrewer_1.1-2 
## [7] lattice_0.20-35     data.table_1.10.4-3 deSolve_1.20       
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_0.12.14         mvtnorm_1.0-6        gtools_3.5.0        
##  [4] zoo_1.8-1            assertthat_0.2.0     rprojroot_1.3-2     
##  [7] digest_0.6.13        mime_0.5             R6_2.2.2            
## [10] plyr_1.8.4           backports_1.1.2      stats4_3.4.3        
## [13] evaluate_0.10.1      coda_0.19-1          colourpicker_1.0    
## [16] blogdown_0.4         pillar_1.0.1         rlang_0.1.6         
## [19] lazyeval_0.2.1       miniUI_0.1.1         Matrix_1.2-12       
## [22] DT_0.2               rmarkdown_1.8        shinythemes_1.1.1   
## [25] shinyjs_0.9.1        stringr_1.2.0        htmlwidgets_0.9     
## [28] loo_1.1.0            igraph_1.1.2         munsell_0.4.3       
## [31] shiny_1.0.5          compiler_3.4.3       httpuv_1.3.5        
## [34] pkgconfig_2.0.1      base64enc_0.1-3      rstantools_1.4.0    
## [37] htmltools_0.3.6      tibble_1.4.1         gridExtra_2.3       
## [40] bookdown_0.5         threejs_0.3.1        matrixStats_0.52.2  
## [43] dplyr_0.7.4          brms_2.0.1           grid_3.4.3          
## [46] xtable_1.8-2         gtable_0.2.0         magrittr_1.5        
## [49] scales_0.5.0         stringi_1.1.6        reshape2_1.4.3      
## [52] bindrcpp_0.2         dygraphs_1.1.1.4     xts_0.10-1          
## [55] tools_3.4.3          glue_1.2.0           markdown_0.8        
## [58] shinystan_2.4.0      crosstalk_1.0.0      rsconnect_0.8.5     
## [61] abind_1.4-5          parallel_3.4.3       yaml_2.1.16         
## [64] inline_0.3.14        colorspace_1.3-2     bridgesampling_0.4-0
## [67] bayesplot_1.4.0      knitr_1.18           bindr_0.1           
## [70] Brobdingnag_1.2-4</code></pre>
</div>
<div>
<h1>References</h1>
<div>
<div>
<p>Bürkner, Paul-Christian. 2017. “brms: An R Package for Bayesian Multilevel Models Using Stan.” <em>Journal of Statistical Software</em> 80 (1): 1–28. doi:<a rel="nofollow" href="https://doi.org/10.18637/jss.v080.i01" target="_blank">10.18637/jss.v080.i01</a>.</p>
</div>
<div>
<p>Clark, David R. 2003. <em>LDF Curve-Fitting and Stochastic Reserving: A Maximum Likelihood Approach</em>. Casualty Actuarial Society; <a rel="nofollow" href="http://www.casact.org/pubs/forum/03fforum/03ff041.pdf" target="_blank">http://www.casact.org/pubs/forum/03fforum/03ff041.pdf</a>.</p>
</div>
<div>
<p>Guszcza, James. 2008. “Hierarchical Growth Curve Models for Loss Reserving.” In, 146–73. CAS Fall Forum; <a rel="nofollow" href="https://www.casact.org/pubs/forum/08fforum/7Guszcza.pdf" target="_blank">https://www.casact.org/pubs/forum/08fforum/7Guszcza.pdf</a>.</p>
</div>
<div>
<p>Lunn, David J, Andrew Thomas, Nicky Best, and David Spiegelhalter. 2000. “WinBUGS-a Bayesian Modelling Framework: Concepts, Structure, and Extensibility.” <em>Statistics and Computing</em> 10 (4). <a rel="nofollow" href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.471.604&rep=rep1&type=pdf" target="_blank">http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.471.604&amp;rep=rep1&amp;type=pdf</a>; Springer: 325–37.</p>
</div>
<div>
<p>Miranda, María Dolores Martínez, Jens Perch Nielsen, and Richard Verrall. 2012. “Double Chain Ladder.” <em>ASTIN Bulletin: The Journal of the IAA</em> 42 (1). <a rel="nofollow" href="https://www.cass.city.ac.uk/__data/assets/pdf_file/0011/364961/double-chain-ladder-cass-knowledge.pdf" target="_blank">https://www.cass.city.ac.uk/__data/assets/pdf_file/0011/364961/double-chain-ladder-cass-knowledge.pdf</a>; Cambridge University Press: 59–76.</p>
</div>
<div>
<p>Morris, Jake. 2016. “Hierarchical Compartmental Models for Loss Reserving.” In. Casualty Actuarial Society Summer E-Forum; <a rel="nofollow" href="https://www.casact.org/pubs/forum/16sforum/Morris.pdf" target="_blank">https://www.casact.org/pubs/forum/16sforum/Morris.pdf</a>.</p>
</div>
<div>
<p>Quarg, Gerhard, and Thomas Mack. 2004. “Munich Chain Ladder.” Munich Re Group: <a rel="nofollow" href="http://www.variancejournal.org/issues/02-02/266.pdf" target="_blank">http://www.variancejournal.org/issues/02-02/266.pdf</a>.</p>
</div>
<div>
<p>Stan Development Team. 2017. “RStan: The R Interface to Stan.” <a rel="nofollow" href="http://mc-stan.org/" target="_blank">http://mc-stan.org/</a>.</p>
</div>
<div>
<p>Zhang, Yanwei, Vanja Dukic, and James Guszcza. 2012. “A Bayesian Non-Linear Model for Forecasting Insurance Loss Payments.” <em>Journal of the Royal Statistical Society: Series A (Statistics in Society)</em> 175 (2). Wiley Online Library: 637–56.</p>
</div>
</div>
</div>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://magesblog.com/post/hierarchical-compartmental-reserving-models/"> R on mages' blog</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/OPfEmJJUf-I" height="1"></a></div>