Introduction to fastlife#

This notebooks explores the fastlife model, a parallel processing model, by taking a closer look at some of the Spaces unique to the fastlife model.

If you’re viewing this page as a static HTML page on https://lifelib.io, the same contents are also available here on binder as Jupyter notebook executable online (it may take a while to load). To run this notebook and get all the outputs below, Go to the Cell menu above, and then click Run All.

Click the badge below to run this notebook online on Google Colab. You need a Google account and need to be logged in to it to run this notebook on Google Colab. Run on Google Colab

The next code cell below is relevant only when you run this notebook on Google Colab. It installs lifelib and creates a copy of the library for this notebook.

[ ]:
import sys, os

if 'google.colab' in sys.modules:
    lib = 'fastlife'; lib_dir = '/content/'+ lib
    if not os.path.exists(lib_dir):
        !pip install lifelib
        import lifelib; lifelib.create(lib, lib_dir)

    %cd $lib_dir

Reading the fastlife model#

The fastlife model is saved as a folder named model in the fastlife folder. To create a live model, import modelx and call read_model function by passing the folder path.

[1]:
import modelx as mx
model = mx.read_model("model")

The previously created model is renamed automatically to avoid name conflict. To get all existing models, get_models modelx API function can be used. get_models returns a dict of all the existing models associated with their names.

[2]:
import modelx as mx
mx.get_models()
[2]:
{'fastlife': <Model fastlife>}

Calculating the results in the Projection Space#

The present values of net cashflows are calculated in PV_NetCashsflow Cells in the Projection Space.

[14]:
model.Projection.PV_NetCashflow(0)
[14]:
Policy
1      8.954018e+03
2      7.511092e+03
3      9.173907e+03
4      7.638071e+03
5      9.418541e+03
           ...
296    2.599794e+06
297    2.298079e+06
298    2.557191e+06
299    2.242406e+06
300    2.510715e+06
Length: 300, dtype: float64

Unlike the simplelife model, PV_NetCashflow returns a pandas Seris object with Policy index. Each element of the returned Series is the present value of the net cashflow of each model point. Below is the formula of PV_NetCashflow.

[13]:
model.Projection.PV_NetCashflow.formula
[13]:
def PV_NetCashflow(t):
    """Present value of net cashflow"""
    return (PV_PremIncome(t)
            + PV_ExpsTotal(t)
            + PV_BenefitTotal(t))

As you see, PV_NetCashflow at time 0 is the sum of PV_PremIncome, PV_ExpsTotal and PV_BenefitTotal.

[16]:
model.Projection.PV_PremIncome(0)
[16]:
Policy
1      2.932812e+04
2      2.418886e+04
3      3.019898e+04
4      2.466945e+04
5      3.118342e+04
           ...
296    3.218643e+06
297    3.066867e+06
298    3.198456e+06
299    3.038678e+06
300    3.176323e+06
Name: PV_PremIncome, Length: 300, dtype: float64
[18]:
model.Projection.PV_PremIncome.formula
[18]:
def PV_PremIncome(t):
    """Present value of premium income"""

    exist = (t <= last_t())

    if not exist.any():
        return 0
    else:
        result = exist * PremIncome(t) + PV_PremIncome(t+1) / (1 + DiscRate(t))
        result.name = "PV_PremIncome"
        return result

Most of the Cells in Projection Space operate on Serieses indexed by Policy just like PV_NetCashflow, because their precedent Cells operate on Serieses with the same index.

The Policy Space#

The Projection Space have a child Space named Policy. Policy contains policy data and Cells to calculate policyholder values. The PolicyData Reference holds a PandasData object, which internaly stores policy data read from an input file as a pandas DataFrame.

[22]:
model.Projection.Policy.PolicyData
[22]:
<modelx.io.pandasio.PandasData at 0x24259490b48>

To get the DataFrame stored in the PolicyData object, call it:

[21]:
model.Projection.Policy.PolicyData()
[21]:
Product PolicyType Gen Channel Duration Sex IssueAge PaymentMode PremFreq PolicyTerm MaxPolicyTerm PolicyCount SumAssured
Policy
1 TERM 1 1 NaN 0 M 30 1 12 15 65 1 1000000
2 TERM 1 1 NaN 0 F 30 1 12 15 65 1 1000000
3 TERM 1 1 NaN 0 M 31 1 12 15 64 1 1000000
4 TERM 1 1 NaN 0 F 31 1 12 15 64 1 1000000
5 TERM 1 1 NaN 0 M 32 1 12 15 63 1 1000000
... ... ... ... ... ... ... ... ... ... ... ... ... ...
296 ENDW 1 1 NaN 0 F 77 1 12 10 10 1 1000000
297 ENDW 1 1 NaN 0 M 78 1 12 10 10 1 1000000
298 ENDW 1 1 NaN 0 F 78 1 12 10 10 1 1000000
299 ENDW 1 1 NaN 0 M 79 1 12 10 10 1 1000000
300 ENDW 1 1 NaN 0 F 79 1 12 10 10 1 1000000

300 rows × 13 columns

PolicyData is a PandasData object, and it was created by the new_pandas method of UserSpace. The location of the input file can be acquired as its path attribute.

[26]:
model.Projection.Policy.PolicyData.path
[26]:
WindowsPath('Input/PoliyData.xlsx')

There are many Cells in Policy whose roles are for calculating policyholder values such as premiums and cash surrender values from commutation functions and actuarial notations. For example, GrossPremRate is for calculating gross premium rates:

[29]:
model.Projection.Policy.GrossPremRate.formula
[29]:
def GrossPremRate():
    """Gross Premium Rate per Sum Assured per payment"""

    def get_value(pol):

        prod = pol['Product']
        alpha = pol['LoadAcqSA']
        beta = pol['LoadMaintPrem']
        delta = pol['LoadMaintPrem2']
        gamma = pol['LoadMaintSA']
        gamma2 = pol['LoadMaintSA2']
        freq = pol['PremFreq']

        x, n, m = pol['IssueAge'], pol['PolicyTerm'], pol['PolicyTerm']

        comf = LifeTable[pol['Sex'], pol['IntRate_PREM'], pol['TableID_PREM']]

        if prod == 'TERM' or prod == 'WL':
            return (comf.Axn(x, n) + alpha + gamma * comf.AnnDuenx(x, n, freq)
                    + gamma2 * comf.AnnDuenx(x, n-m, 1, m)) / (1-beta-delta) / freq / comf.AnnDuenx(x, m, freq)

        elif prod == 'ENDW':
            return (comf.Exn(x, n) + comf.Axn(x, n) + alpha + gamma * comf.AnnDuenx(x, n, freq)
                    + gamma2 * comf.AnnDuenx(x, n-m, 1, m)) / (1-beta-delta) / freq / comf.AnnDuenx(x, m, freq)
        else:
            raise ValueError('invalid product')


    result = PolicyDataExt1().apply(get_value, axis=1)
    result.name = 'GrossPremRate'

    return result

As we see in the Projection Space, GrossPremRate also retuns results for all model points in a Series with the Policy index.

[28]:
model.Projection.Policy.GrossPremRate()
[28]:
Policy
1      0.000298
2      0.000245
3      0.000307
4      0.000250
5      0.000317
         ...
296    0.043845
297    0.045945
298    0.044096
299    0.046412
300    0.044383
Name: GrossPremRate, Length: 300, dtype: float64

The Assumptions Space#

Projection has another space named Assumptions. Assumptions associates projection assumptions to model points, by looking up paramters in a table stored as an ExcelRange object associated to a Reference named Assumption.

[32]:
model.Projection.Assumptions.Assumption
[32]:
<modelx.io.excelio.ExcelRange at 0x24259490e88>

Just like PandasData objects, Assumption has the path attribute hoding a path to its input file.

[38]:
model.Projection.Assumptions.Assumption.path
[38]:
WindowsPath('Input/input.xlsx')

Assumption is a dict-like object, whose keys are tuples of assumption type, product ID, policy type ID and genration ID. For example, For the assumption type ‘Surrender’ and product ‘TERM’

[36]:
model.Projection.Assumptions.Assumption["Surrender", "TERM", None, None]
[36]:
'LapseRate1'

Most of the Cells in the Assumption Space are for lookup operations just like the above example. The lookup results are also in Series.

[37]:
model.Projection.Assumptions.SurrRateID()
[37]:
Policy
1      LapseRate1
2      LapseRate1
3      LapseRate1
4      LapseRate1
5      LapseRate1
          ...
296    LapseRate1
297    LapseRate1
298    LapseRate1
299    LapseRate1
300    LapseRate1
Length: 300, dtype: object