Experience summaries

After experience data has been prepared for analysis, the next step is to summarize results. The actxps package’s workhorse function for summarizing termination experience is the exp_stats() method of the ExposedDF class. This function returns an ExpStats object, which is a type of data frame containing additional attributes about the experience study.

At a minimum, an ExpStats includes:

Optionally, an ExpStats can also include:

To demonstrate this function, we’re going to use a data frame containing simulated census data for a theoretical deferred annuity product that has an optional guaranteed income rider. Before exp_stats() can be used, we must convert our census data into exposure records using the ExposedDF() class1. In addition, let’s assume we’re interested in studying surrender rates, so we’ll pass the argument target_status='Surrender' to ExposedDF().

import actxps as xp
import polars as pl
import numpy as np

census_dat = xp.load_census_dat()
exposed_data = xp.ExposedDF(census_dat, end_date="2019-12-31",
                            target_status="Surrender")

The exp_stats() method

To use exp_stats(), simply call the method from an ExposedDF object.

exposed_data.exp_stats()
Experience study results

Target status: Surrender
Study range: 1900-01-01 to 2019-12-31

shape: (1, 4)
┌──────────┬────────┬───────────────┬──────────┐
│ n_claims ┆ claims ┆ exposure      ┆ q_obs    │
│ ---      ┆ ---    ┆ ---           ┆ ---      │
│ u32      ┆ u32    ┆ f64           ┆ f64      │
╞══════════╪════════╪═══════════════╪══════════╡
│ 2869     ┆ 2869   ┆ 132633.900756 ┆ 0.021631 │
└──────────┴────────┴───────────────┴──────────┘

The results show us that we specified no groups, which is why the output data is a single row. In addition, we can see that we’re looking at surrender rates through the end of 2019, which exp_stats() inferred from exposed_data.

The number of claims (n_claims) is equal to the number of “Surrender” statuses in exposed_data. Since we didn’t specify any weighting variable, the amount of claims (claims) equals the number of claims.

amount = sum(exposed_data.data["status"] == "Surrender")
amount
2869

The total exposure (exposure) is equal to the sum of the exposures in exposed_data. Had we specified a weighting variable, this would be equal to the sum of weighted exposures.

sum_expo = sum(exposed_data.data['exposure'])
sum_expo
132633.90075604623

Lastly, the observed termination rate (q_obs) equals the amount of claims divided by the exposures.

amount / sum_expo
0.021630970541060664

Grouped data

If the data is grouped using the group_by() method, future calls to exp_stats() will contain one record for each unique group.

In the following, exposed_data is grouped by policy year before exp_stats() is called. This results in one row per policy year found in the data.

(exposed_data.
    group_by('pol_yr').
    exp_stats())
Experience study results

Groups: pol_yr
Target status: Surrender
Study range: 1900-01-01 to 2019-12-31

shape: (15, 5)
┌────────┬──────────┬────────┬──────────────┬──────────┐
│ pol_yr ┆ n_claims ┆ claims ┆ exposure     ┆ q_obs    │
│ ---    ┆ ---      ┆ ---    ┆ ---          ┆ ---      │
│ u32    ┆ u32      ┆ u32    ┆ f64          ┆ f64      │
╞════════╪══════════╪════════╪══════════════╪══════════╡
│ 1      ┆ 102      ┆ 102    ┆ 19252.207785 ┆ 0.005298 │
│ 2      ┆ 160      ┆ 160    ┆ 17714.766674 ┆ 0.009032 │
│ 3      ┆ 124      ┆ 124    ┆ 16097.130504 ┆ 0.007703 │
│ 4      ┆ 168      ┆ 168    ┆ 14535.853836 ┆ 0.011558 │
│ 5      ┆ 164      ┆ 164    ┆ 12915.522726 ┆ 0.012698 │
│ …      ┆ …        ┆ …      ┆ …            ┆ …        │
│ 11     ┆ 804      ┆ 804    ┆ 4390.391496  ┆ 0.183127 │
│ 12     ┆ 330      ┆ 330    ┆ 2663.379931  ┆ 0.123903 │
│ 13     ┆ 99       ┆ 99     ┆ 1619.90935   ┆ 0.061115 │
│ 14     ┆ 62       ┆ 62     ┆ 871.838738   ┆ 0.071114 │
│ 15     ┆ 17       ┆ 17     ┆ 268.175058   ┆ 0.063391 │
└────────┴──────────┴────────┴──────────────┴──────────┘

Multiple grouping variables are allowed. Below, the presence of an income guarantee (inc_guar) is added as a second grouping variable.

(exposed_data.
    group_by('inc_guar', 'pol_yr').
    exp_stats())
Experience study results

Groups: inc_guar, pol_yr
Target status: Surrender
Study range: 1900-01-01 to 2019-12-31

shape: (30, 6)
┌──────────┬────────┬──────────┬────────┬─────────────┬──────────┐
│ inc_guar ┆ pol_yr ┆ n_claims ┆ claims ┆ exposure    ┆ q_obs    │
│ ---      ┆ ---    ┆ ---      ┆ ---    ┆ ---         ┆ ---      │
│ bool     ┆ u32    ┆ u32      ┆ u32    ┆ f64         ┆ f64      │
╞══════════╪════════╪══════════╪════════╪═════════════╪══════════╡
│ false    ┆ 1      ┆ 56       ┆ 56     ┆ 7719.80545  ┆ 0.007254 │
│ false    ┆ 2      ┆ 92       ┆ 92     ┆ 7102.810869 ┆ 0.012953 │
│ false    ┆ 3      ┆ 67       ┆ 67     ┆ 6446.913856 ┆ 0.010393 │
│ false    ┆ 4      ┆ 123      ┆ 123    ┆ 5798.905846 ┆ 0.021211 │
│ false    ┆ 5      ┆ 97       ┆ 97     ┆ 5105.874571 ┆ 0.018998 │
│ …        ┆ …      ┆ …        ┆ …      ┆ …           ┆ …        │
│ true     ┆ 11     ┆ 347      ┆ 347    ┆ 2696.774512 ┆ 0.128672 │
│ true     ┆ 12     ┆ 150      ┆ 150    ┆ 1768.300449 ┆ 0.084827 │
│ true     ┆ 13     ┆ 49       ┆ 49     ┆ 1117.137361 ┆ 0.043862 │
│ true     ┆ 14     ┆ 29       ┆ 29     ┆ 609.216476  ┆ 0.047602 │
│ true     ┆ 15     ┆ 9        ┆ 9      ┆ 194.128602  ┆ 0.046361 │
└──────────┴────────┴──────────┴────────┴─────────────┴──────────┘

Grouping persists after group_by() is called. To remove groups, the ungroup() method can be used.

# check groups
print(exposed_data.groups)
['inc_guar', 'pol_yr']
# ungroup, then check groups again
exposed_data.ungroup()
print(exposed_data.groups is None)
True

Target status

The target_status argument of exp_stats() specifies which status levels count as claims in the experience study summary. If the ExposedDF object already has a specified target status (from the original call to ExposedDF()), then this argument is not necessary because the target status is automatically inferred.

Even if the target status exists, it can be overridden. However care should be taken to ensure that exposure values in the data are appropriate for the new status.

Using the example data, a total termination rate can be estimated by including both death and surrender statuses in target_status. To ensure exposures are accurate, an adjustment is made to fully expose deaths prior to calling exp_stats()2.

from copy import deepcopy

exposed_data2 = deepcopy(exposed_data)
exposed_data2.data = exposed_data2.data.with_columns(
    exposure=pl.when(pl.col('status') == "Death").
             then(1).
             otherwise('exposure'))

(exposed_data2.group_by('pol_yr').
    exp_stats(target_status=["Surrender", "Death"]))
Experience study results

Groups: pol_yr
Target status: Surrender, Death
Study range: 1900-01-01 to 2019-12-31

shape: (15, 5)
┌────────┬──────────┬────────┬──────────────┬──────────┐
│ pol_yr ┆ n_claims ┆ claims ┆ exposure     ┆ q_obs    │
│ ---    ┆ ---      ┆ ---    ┆ ---          ┆ ---      │
│ u32    ┆ u32      ┆ u32    ┆ f64          ┆ f64      │
╞════════╪══════════╪════════╪══════════════╪══════════╡
│ 1      ┆ 290      ┆ 290    ┆ 19348.981503 ┆ 0.014988 │
│ 2      ┆ 325      ┆ 325    ┆ 17796.237974 ┆ 0.018262 │
│ 3      ┆ 292      ┆ 292    ┆ 16180.423288 ┆ 0.018046 │
│ 4      ┆ 329      ┆ 329    ┆ 14611.259915 ┆ 0.022517 │
│ 5      ┆ 329      ┆ 329    ┆ 12997.509829 ┆ 0.025313 │
│ …      ┆ …        ┆ …      ┆ …            ┆ …        │
│ 11     ┆ 894      ┆ 894    ┆ 4441.488278  ┆ 0.201284 │
│ 12     ┆ 398      ┆ 398    ┆ 2696.530653  ┆ 0.147597 │
│ 13     ┆ 131      ┆ 131    ┆ 1634.27666   ┆ 0.080158 │
│ 14     ┆ 89       ┆ 89     ┆ 886.369047   ┆ 0.10041  │
│ 15     ┆ 23       ┆ 23     ┆ 272.057407   ┆ 0.084541 │
└────────┴──────────┴────────┴──────────────┴──────────┘

Weighted results

Experience studies often weight output by key policy values. Examples include account values, cash values, face amount, premiums, and more. Weighting can be accomplished by passing the name of a weighting column to the wt argument of exp_stats().

Our sample data contains a column called premium that we can weight by. When weights are supplied, the claims, exposure, and q_obs columns will be weighted. If expected termination rates are supplied (see below), these rates and A/E values will also be weighted.3

#  label: weight-res
(exposed_data.
    group_by('pol_yr').
    exp_stats(wt='premium'))
Experience study results

Groups: pol_yr
Target status: Surrender
Study range: 1900-01-01 to 2019-12-31
Weighted by: premium

shape: (15, 8)
┌────────┬──────────┬──────────┬───────────────┬──────────┬─────────────┬───────────┬──────────┐
│ pol_yr ┆ n_claims ┆ claims   ┆ exposure      ┆ q_obs    ┆ weight      ┆ weight_sq ┆ weight_n │
│ ---    ┆ ---      ┆ ---      ┆ ---           ┆ ---      ┆ ---         ┆ ---       ┆ ---      │
│ u32    ┆ u32      ┆ f64      ┆ f64           ┆ f64      ┆ f64         ┆ f64       ┆ i32      │
╞════════╪══════════╪══════════╪═══════════════╪══════════╪═════════════╪═══════════╪══════════╡
│ 1      ┆ 102      ┆ 83223.0  ┆ 2.5313e7      ┆ 0.003288 ┆ 2.6301746e7 ┆ 6.0743e10 ┆ 19995    │
│ 2      ┆ 160      ┆ 170058.0 ┆ 2.3352e7      ┆ 0.007282 ┆ 2.4275265e7 ┆ 5.6233e10 ┆ 18434    │
│ 3      ┆ 124      ┆ 123554.0 ┆ 2.1247e7      ┆ 0.005815 ┆ 2.2201817e7 ┆ 5.1747e10 ┆ 16806    │
│ 4      ┆ 168      ┆ 176751.0 ┆ 1.9271e7      ┆ 0.009172 ┆ 2.0200019e7 ┆ 4.7142e10 ┆ 15266    │
│ 5      ┆ 164      ┆ 173273.0 ┆ 1.7229e7      ┆ 0.010057 ┆ 1.8134795e7 ┆ 4.2888e10 ┆ 13618    │
│ …      ┆ …        ┆ …        ┆ …             ┆ …        ┆ …           ┆ …         ┆ …        │
│ 11     ┆ 804      ┆ 856379.0 ┆ 6.0932e6      ┆ 0.140547 ┆ 6.783273e6  ┆ 1.6220e10 ┆ 4897     │
│ 12     ┆ 330      ┆ 383055.0 ┆ 3.8835e6      ┆ 0.098636 ┆ 4.525027e6  ┆ 1.1191e10 ┆ 3093     │
│ 13     ┆ 99       ┆ 123357.0 ┆ 2.4503e6      ┆ 0.050344 ┆ 2.891573e6  ┆ 7.0759e9  ┆ 1937     │
│ 14     ┆ 62       ┆ 75534.0  ┆ 1.3392e6      ┆ 0.056401 ┆ 1.821026e6  ┆ 4.6557e9  ┆ 1182     │
│ 15     ┆ 17       ┆ 19168.0  ┆ 401169.299296 ┆ 0.04778  ┆ 783146.0    ┆ 1.9448e9  ┆ 510      │
└────────┴──────────┴──────────┴───────────────┴──────────┴─────────────┴───────────┴──────────┘

Expected values and A/E ratios

As common metric in experience studies is the actual-to-expected, or A/E ratio.

\[ A/E\ ratio = \frac{observed\ value}{expected\ value} \]

If the data passed to exp_stats() has one or more columns containing expected termination rates, A/E ratios can be calculated by passing the names of these columns to the expected argument.

Let’s assume we have two sets of expected rates. The first set is a vector that varies by policy year. The second set is either 1.5% or 3.0% depending on whether the policy has a guaranteed income benefit. First, we need to attach these assumptions to our exposure data. We will use the names expected_1 and expected_2. Then we pass these names to the expected argument when we call exp_stats().

In the output, 4 new columns are created for expected rates and A/E ratios.

expected_table = np.concatenate((np.linspace(0.005, 0.03, 10),
                                 [.2, .15], np.repeat(0.05, 3)))

# using 2 different expected termination rates
exposed_data.data = exposed_data.data.with_columns(
    expected_1=expected_table[exposed_data.data['pol_yr'] - 1],
    expected_2=pl.when(pl.col('inc_guar')).then(0.015).otherwise(0.03)
)

exp_res = (exposed_data.
           group_by("pol_yr", "inc_guar").
           exp_stats(expected=["expected_1", "expected_2"]))

exp_res.data.select('pol_yr', 'inc_guar', 'q_obs', pl.col('^.*expected.*$'))
shape: (30, 7)
pol_yr inc_guar q_obs expected_1 expected_2 ae_expected_1 ae_expected_2
u32 bool f64 f64 f64 f64 f64
1 false 0.007254 0.005 0.03 1.450814 0.241802
1 true 0.003989 0.005 0.015 0.797752 0.265917
2 false 0.012953 0.007778 0.03 1.665337 0.431754
2 true 0.006408 0.007778 0.015 0.823869 0.427191
3 false 0.010393 0.010556 0.03 0.984559 0.346419
13 true 0.043862 0.05 0.015 0.877242 2.924141
14 false 0.125656 0.05 0.03 2.513115 4.188525
14 true 0.047602 0.05 0.015 0.952043 3.173475
15 false 0.10804 0.05 0.03 2.160806 3.601343
15 true 0.046361 0.05 0.015 0.92722 3.090735

As noted above, if weights are passed to exp_stats() then A/E ratios will also be weighted.

exp_res_wt = (exposed_data.
              group_by('pol_yr', 'inc_guar').
              exp_stats(expected=["expected_1", "expected_2"],
                        wt="premium"))

exp_res_wt.data.select('pol_yr', 'inc_guar', 'q_obs', pl.col('^.*expected.*$'))
shape: (30, 7)
pol_yr inc_guar q_obs expected_1 expected_2 ae_expected_1 ae_expected_2
u32 bool f64 f64 f64 f64 f64
1 false 0.004709 0.005 0.03 0.94182 0.15697
1 true 0.002351 0.005 0.015 0.470113 0.156704
2 false 0.01054 0.007778 0.03 1.355163 0.351339
2 true 0.005132 0.007778 0.015 0.659867 0.342153
3 false 0.007373 0.010556 0.03 0.698464 0.245756
13 true 0.038539 0.05 0.015 0.770787 2.569291
14 false 0.096951 0.05 0.03 1.939013 3.231688
14 true 0.036829 0.05 0.015 0.736585 2.455285
15 false 0.066551 0.05 0.03 1.331026 2.218377
15 true 0.039654 0.05 0.015 0.793086 2.64362

Credibility

If the credibility argument is set to True, exp_stats() will produce an estimate of partial credibility under the Limited Fluctuation credibility method (also known as Classical Credibility) assuming a binomial distribution of claims.4

(exposed_data.
    group_by('pol_yr', 'inc_guar').
    exp_stats(credibility=True).
    data.select('pol_yr', 'inc_guar', 'claims', 'q_obs', 'credibility'))
shape: (30, 5)
pol_yr inc_guar claims q_obs credibility
u32 bool u32 f64 f64
1 false 56 0.007254 0.191601
1 true 46 0.003989 0.173368
2 false 92 0.012953 0.24629
2 true 68 0.006408 0.211044
3 false 67 0.010393 0.209907
13 true 49 0.043862 0.182625
14 false 33 0.125656 0.156725
14 true 29 0.047602 0.140771
15 false 8 0.10804 0.0764
15 true 9 0.046361 0.07837

Under the default arguments, credibility calculations assume a 95% confidence of being within 5% of the true value. These parameters can be overridden using the conf_level and cred_r arguments, respectively.

(exposed_data.
    group_by('pol_yr', 'inc_guar').
    exp_stats(credibility=True, conf_level=0.98, cred_r=0.03).
    data.select('pol_yr', 'inc_guar', 'claims', 'q_obs', 'credibility'))
shape: (30, 5)
pol_yr inc_guar claims q_obs credibility
u32 bool u32 f64 f64
1 false 56 0.007254 0.096855
1 true 46 0.003989 0.087638
2 false 92 0.012953 0.124501
2 true 68 0.006408 0.106683
3 false 67 0.010393 0.106109
13 true 49 0.043862 0.092318
14 false 33 0.125656 0.079225
14 true 29 0.047602 0.07116
15 false 8 0.10804 0.038621
15 true 9 0.046361 0.039616

If expected values are passed to exp_stats() and credibility is set to True, then the output will also contain credibility-weighted expected values:

\[ q^{adj} = Z^{cred} \times q^{obs} + (1-Z^{cred}) \times q^{exp} \] where,

  • \(q^{adj}\) = credibility-weighted estimate
  • \(Z^{cred}\) = partial credibility factor
  • \(q^{obs}\) = observed termination rate
  • \(q^{exp}\) = expected termination rate
(exposed_data.
    group_by('pol_yr', 'inc_guar').
    exp_stats(credibility=True, expected='expected_1').
    data.select('pol_yr', 'inc_guar', 'claims', 'q_obs', 'credibility',
                pl.col('^.*expected.*$')))
shape: (30, 8)
pol_yr inc_guar claims q_obs credibility expected_1 ae_expected_1 adj_expected_1
u32 bool u32 f64 f64 f64 f64 f64
1 false 56 0.007254 0.191601 0.005 1.450814 0.005432
1 true 46 0.003989 0.173368 0.005 0.797752 0.004825
2 false 92 0.012953 0.24629 0.007778 1.665337 0.009052
2 true 68 0.006408 0.211044 0.007778 0.823869 0.007489
3 false 67 0.010393 0.209907 0.010556 0.984559 0.010521
13 true 49 0.043862 0.182625 0.05 0.877242 0.048879
14 false 33 0.125656 0.156725 0.05 2.513115 0.061857
14 true 29 0.047602 0.140771 0.05 0.952043 0.049662
15 false 8 0.10804 0.0764 0.05 2.160806 0.054434
15 true 9 0.046361 0.07837 0.05 0.92722 0.049715

Confidence intervals

If conf_int is set to True, exp_stats() will produce lower and upper confidence interval limits for the observed termination rate.

(exposed_data.
    group_by('pol_yr', 'inc_guar').
    exp_stats(conf_int=True).
    data.select('pol_yr', 'inc_guar', pl.col('^q_obs.*$')))
shape: (30, 5)
pol_yr inc_guar q_obs q_obs_lower q_obs_upper
u32 bool f64 f64 f64
1 false 0.007254 0.005441 0.009197
1 true 0.003989 0.002862 0.005203
2 false 0.012953 0.010418 0.015628
2 true 0.006408 0.0049 0.00801
3 false 0.010393 0.008066 0.012874
13 true 0.043862 0.032225 0.056394
14 false 0.125656 0.087578 0.167541
14 true 0.047602 0.031188 0.065658
15 false 0.10804 0.040515 0.18907
15 true 0.046361 0.020605 0.077268

If no weighting variable is passed to wt, confidence intervals will be constructed assuming a binomial distribution of claims. However, if a weighting variable is supplied, a normal distribution for aggregate claims will be assumed with a mean equal to observed claims and a variance equal to:

\[ Var(S) = E(N) \times Var(X) + E(X)^2 \times Var(N) \]

Where S is the aggregate claim random variable, X is the weighting variable assumed to follow a normal distribution, and N is a binomial random variable for the number of claims.

The default confidence level is 95%. This can be changed using the conf_level argument. Below, tighter confidence intervals are constructed by decreasing the confidence level to 90%.

(exposed_data.
    group_by('pol_yr', 'inc_guar').
    exp_stats(conf_int = True, conf_level = 0.9).
    data.select('pol_yr', 'inc_guar', pl.col('^q_obs.*$')))
shape: (30, 5)
pol_yr inc_guar q_obs q_obs_lower q_obs_upper
u32 bool f64 f64 f64
1 false 0.007254 0.0057 0.008938
1 true 0.003989 0.003035 0.004943
2 false 0.012953 0.010841 0.015205
2 true 0.006408 0.005183 0.007727
3 false 0.010393 0.008376 0.012564
13 true 0.043862 0.034016 0.053709
14 false 0.125656 0.091386 0.159926
14 true 0.047602 0.034471 0.062375
15 false 0.10804 0.05402 0.175565
15 true 0.046361 0.020605 0.072117

If expected values are passed to expected, the output will also contain confidence intervals around any actual-to-expected ratios.

(exposed_data.
    group_by('pol_yr', 'inc_guar').
    exp_stats(conf_int = True, expected='expected_1').
    data.select('pol_yr', 'inc_guar', pl.col('^ae.*$')))
shape: (30, 5)
pol_yr inc_guar ae_expected_1 ae_expected_1_lower ae_expected_1_upper
u32 bool f64 f64 f64
1 false 1.450814 1.08811 1.839425
1 true 0.797752 0.572301 1.040546
2 false 1.665337 1.33951 2.009265
2 true 0.823869 0.630017 1.029836
3 false 0.984559 0.764136 1.219678
13 true 0.877242 0.644504 1.127883
14 false 2.513115 1.751565 3.35082
14 true 0.952043 0.623752 1.313162
15 false 2.160806 0.810302 3.78141
15 true 0.92722 0.412098 1.545367

Lastly, if credibility is True and expected values are passed to expected, confidence intervals will also be calculated for any credibility-weighted termination rates.

Miscellaneous

Summary method

As noted above, the result of exp_stats() is an ExpStats object. If the summary() function is applied to an ExpStats object, the data will be summarized again and return a higher level ExpStats object.

If no additional arguments are passed, summary() returns a single row of aggregate results.

exp_res.summary()
Experience study results

Target status: Surrender
Study range: 1900-01-01 to 2019-12-31
Expected values: expected_1, expected_2

shape: (1, 8)
┌──────────┬────────┬─────────────┬──────────┬────────────┬────────────┬─────────────┬─────────────┐
│ n_claims ┆ claims ┆ exposure    ┆ q_obs    ┆ expected_1 ┆ expected_2 ┆ ae_expected ┆ ae_expected │
│ ---      ┆ ---    ┆ ---         ┆ ---      ┆ ---        ┆ ---        ┆ _1          ┆ _2          │
│ u32      ┆ u32    ┆ f64         ┆ f64      ┆ f64        ┆ f64        ┆ ---         ┆ ---         │
│          ┆        ┆             ┆          ┆            ┆            ┆ f64         ┆ f64         │
╞══════════╪════════╪═════════════╪══════════╪════════════╪════════════╪═════════════╪═════════════╡
│ 2869     ┆ 2869   ┆ 132633.9007 ┆ 0.021631 ┆ 0.024242   ┆ 0.020895   ┆ 0.892305    ┆ 1.035233    │
│          ┆        ┆ 56          ┆          ┆            ┆            ┆             ┆             │
└──────────┴────────┴─────────────┴──────────┴────────────┴────────────┴─────────────┴─────────────┘

If additional variable names are passed to the summary() method, then the output will group the data by those variables. In our example, if pol_yr is passed to summary(), the output will contain one row per policy year.

exp_res.summary('pol_yr')
Experience study results

Groups: pol_yr
Target status: Surrender
Study range: 1900-01-01 to 2019-12-31
Expected values: expected_1, expected_2

shape: (15, 9)
┌────────┬──────────┬────────┬─────────────┬───┬────────────┬────────────┬────────────┬────────────┐
│ pol_yr ┆ n_claims ┆ claims ┆ exposure    ┆ … ┆ expected_1 ┆ expected_2 ┆ ae_expecte ┆ ae_expecte │
│ ---    ┆ ---      ┆ ---    ┆ ---         ┆   ┆ ---        ┆ ---        ┆ d_1        ┆ d_2        │
│ u32    ┆ u32      ┆ u32    ┆ f64         ┆   ┆ f64        ┆ f64        ┆ ---        ┆ ---        │
│        ┆          ┆        ┆             ┆   ┆            ┆            ┆ f64        ┆ f64        │
╞════════╪══════════╪════════╪═════════════╪═══╪════════════╪════════════╪════════════╪════════════╡
│ 1      ┆ 102      ┆ 102    ┆ 19252.20778 ┆ … ┆ 0.005      ┆ 0.021015   ┆ 1.059619   ┆ 0.252113   │
│        ┆          ┆        ┆ 5           ┆   ┆            ┆            ┆            ┆            │
│ 2      ┆ 160      ┆ 160    ┆ 17714.76667 ┆ … ┆ 0.007778   ┆ 0.021014   ┆ 1.161259   ┆ 0.429803   │
│        ┆          ┆        ┆ 4           ┆   ┆            ┆            ┆            ┆            │
│ 3      ┆ 124      ┆ 124    ┆ 16097.13050 ┆ … ┆ 0.010556   ┆ 0.021008   ┆ 0.72978    ┆ 0.36669    │
│        ┆          ┆        ┆ 4           ┆   ┆            ┆            ┆            ┆            │
│ 4      ┆ 168      ┆ 168    ┆ 14535.85383 ┆ … ┆ 0.013333   ┆ 0.020984   ┆ 0.866822   ┆ 0.550781   │
│        ┆          ┆        ┆ 6           ┆   ┆            ┆            ┆            ┆            │
│ 5      ┆ 164      ┆ 164    ┆ 12915.52272 ┆ … ┆ 0.016111   ┆ 0.02093    ┆ 0.788145   ┆ 0.606686   │
│        ┆          ┆        ┆ 6           ┆   ┆            ┆            ┆            ┆            │
│ …      ┆ …        ┆ …      ┆ …           ┆ … ┆ …          ┆ …          ┆ …          ┆ …          │
│ 11     ┆ 804      ┆ 804    ┆ 4390.391496 ┆ … ┆ 0.2        ┆ 0.020786   ┆ 0.915636   ┆ 8.809981   │
│ 12     ┆ 330      ┆ 330    ┆ 2663.379931 ┆ … ┆ 0.15       ┆ 0.020041   ┆ 0.826018   ┆ 6.182451   │
│ 13     ┆ 99       ┆ 99     ┆ 1619.90935  ┆ … ┆ 0.05       ┆ 0.019656   ┆ 1.222291   ┆ 3.109275   │
│ 14     ┆ 62       ┆ 62     ┆ 871.838738  ┆ … ┆ 0.05       ┆ 0.019518   ┆ 1.422281   ┆ 3.643434   │
│ 15     ┆ 17       ┆ 17     ┆ 268.175058  ┆ … ┆ 0.05       ┆ 0.019142   ┆ 1.267829   ┆ 3.311695   │
└────────┴──────────┴────────┴─────────────┴───┴────────────┴────────────┴────────────┴────────────┘

Similarly, if inc_guar is passed to summary(), the output will contain a row for each unique value in inc_guar.

exp_res.summary('inc_guar')
Experience study results

Groups: inc_guar
Target status: Surrender
Study range: 1900-01-01 to 2019-12-31
Expected values: expected_1, expected_2

shape: (2, 9)
┌──────────┬──────────┬────────┬────────────┬───┬────────────┬────────────┬────────────┬───────────┐
│ inc_guar ┆ n_claims ┆ claims ┆ exposure   ┆ … ┆ expected_1 ┆ expected_2 ┆ ae_expecte ┆ ae_expect │
│ ---      ┆ ---      ┆ ---    ┆ ---        ┆   ┆ ---        ┆ ---        ┆ d_1        ┆ ed_2      │
│ bool     ┆ u32      ┆ u32    ┆ f64        ┆   ┆ f64        ┆ f64        ┆ ---        ┆ ---       │
│          ┆          ┆        ┆            ┆   ┆            ┆            ┆ f64        ┆ f64       │
╞══════════╪══════════╪════════╪════════════╪═══╪════════════╪════════════╪════════════╪═══════════╡
│ false    ┆ 1601     ┆ 1601   ┆ 52123.2158 ┆ … ┆ 0.023481   ┆ 0.03       ┆ 1.308099   ┆ 1.023856  │
│          ┆          ┆        ┆ 84         ┆   ┆            ┆            ┆            ┆           │
│ true     ┆ 1268     ┆ 1268   ┆ 80510.6848 ┆ … ┆ 0.024734   ┆ 0.015      ┆ 0.636752   ┆ 1.049964  │
│          ┆          ┆        ┆ 72         ┆   ┆            ┆            ┆            ┆           │
└──────────┴──────────┴────────┴────────────┴───┴────────────┴────────────┴────────────┴───────────┘

Column names

As a default, exp_stats() assumes the input data frame uses the following naming conventions:

  • The exposure column is called exposure
  • The status column is called status

These default names can be overridden using the col_exposure and col_status arguments.

For example, if the status column was called curr_stat in our data, we could write:

exposed_data.exp_stats(col_status="curr_stat")

Limitations

The exp_stats() method only supports termination studies. It does not contain support for transaction studies or studies with multiple changes from an active to an inactive status. For information on transaction studies, see Transactions.

Footnotes

  1. See Exposures for more information on creating exposure records.↩︎

  2. This adjustment is not necessary on surrenders because the expose() function previously did this for us.↩︎

  3. When weights are supplied, additional columns are created containing the sum of weights, the sum of squared weights, and the number of records. These columns are used for re-summarizing the data (see the “Summary method” section on this page).↩︎

  4. See Herzog, Thomas (1999). Introduction to Credibility Theory for more information on Limited Fluctuation Credibility.↩︎