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.
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