CPUvsCUDA

Created Diff never expires
23 removals
950 lines
36 additions
958 lines
"""The main Space in the :mod:`~basiclife.BasicTerm_ME` model.
"""The main Space in the :mod:`~basiclife.BasicTerm_ME` model.


:mod:`~basiclife.BasicTerm_ME.Projection` is the only Space defined
:mod:`~basiclife.BasicTerm_ME.Projection` is the only Space defined
in the :mod:`~basiclife.BasicTerm_ME` model, and it contains
in the :mod:`~basiclife.BasicTerm_ME` model, and it contains
all the logic and data used in the model.
all the logic and data used in the model.


.. rubric:: Parameters and References
.. rubric:: Parameters and References


(In all the sample code below,
(In all the sample code below,
the global variable ``Projection`` refers to the
the global variable ``Projection`` refers to the
:mod:`~basiclife.BasicTerm_ME.Projection` Space.)
:mod:`~basiclife.BasicTerm_ME.Projection` Space.)


Attributes:
Attributes:


model_point_table: All model points as a DataFrame.
model_point_table: All model points as a DataFrame.
The sample model point data was generated by
The sample model point data was generated by
*generate_model_points_with_duration.ipynb* included in the library.
*generate_model_points_with_duration.ipynb* included in the library.
By default, :func:`model_point` returns this
By default, :func:`model_point` returns this
entire :attr:`model_point_table`.
entire :attr:`model_point_table`.
The DataFrame has an index named ``point_id``,
The DataFrame has an index named ``point_id``,
and has the following columns:
and has the following columns:


* ``age_at_entry``
* ``age_at_entry``
* ``sex``
* ``sex``
* ``policy_term``
* ``policy_term``
* ``policy_count``
* ``policy_count``
* ``sum_assured``
* ``sum_assured``
* ``duration_mth``
* ``duration_mth``


Cells defined in :mod:`~basiclife.BasicTerm_SE.Projection`
Cells defined in :mod:`~basiclife.BasicTerm_SE.Projection`
with the same names as these columns return
with the same names as these columns return
the corresponding columns.
the corresponding columns.




.. code-block::
.. code-block::


>>> Projection.model_poit_table
>>> Projection.model_poit_table
age_at_entry sex ... sum_assured duration_mth
age_at_entry sex ... sum_assured duration_mth
policy_id ...
policy_id ...
1 47 M ... 622000 1
1 47 M ... 622000 1
2 29 M ... 752000 210
2 29 M ... 752000 210
3 51 F ... 799000 15
3 51 F ... 799000 15
4 32 F ... 422000 125
4 32 F ... 422000 125
5 28 M ... 605000 55
5 28 M ... 605000 55
... .. ... ... ...
... .. ... ... ...
9996 47 M ... 827000 157
9996 47 M ... 827000 157
9997 30 M ... 826000 168
9997 30 M ... 826000 168
9998 45 F ... 783000 146
9998 45 F ... 783000 146
9999 39 M ... 302000 11
9999 39 M ... 302000 11
10000 22 F ... 576000 166
10000 22 F ... 576000 166


[10000 rows x 6 columns]
[10000 rows x 6 columns]


The DataFrame is saved in the Excel file *model_point_table.xlsx*
The DataFrame is saved in the Excel file *model_point_table.xlsx*
placed in the model folder.
placed in the model folder.
:attr:`model_point_table` is created by
:attr:`model_point_table` is created by
Projection's `new_pandas`_ method,
Projection's `new_pandas`_ method,
so that the DataFrame is saved in the separate file.
so that the DataFrame is saved in the separate file.
The DataFrame has the injected attribute
The DataFrame has the injected attribute
of ``_mx_dataclident``::
of ``_mx_dataclident``::


>>> Projection.model_point_table._mx_dataclient
>>> Projection.model_point_table._mx_dataclient
<PandasData path='model_point_table.xlsx' filetype='excel'>
<PandasData path='model_point_table.xlsx' filetype='excel'>


.. seealso::
.. seealso::


* :func:`model_point`
* :func:`model_point`
* :func:`age_at_entry`
* :func:`age_at_entry`
* :func:`sex`
* :func:`sex`
* :func:`policy_term`
* :func:`policy_term`
* :func:`pols_if_init`
* :func:`pols_if_init`
* :func:`sum_assured`
* :func:`sum_assured`
* :func:`duration_mth`
* :func:`duration_mth`


premium_table: Premium rate table by entry age and duration as a Series.
premium_table: Premium rate table by entry age and duration as a Series.
The table is created using :mod:`~basiclife.BasicTerm_M`
The table is created using :mod:`~basiclife.BasicTerm_M`
as demonstrated in *create_premium_table.ipynb*.
as demonstrated in *create_premium_table.ipynb*.
The table is stored in *premium_table.xlsx* in the model folder.
The table is stored in *premium_table.xlsx* in the model folder.


.. code-block::
.. code-block::


>>> Projection.premium_table
>>> Projection.premium_table
age_at_entry policy_term
age_at_entry policy_term
20 10 0.000046
20 10 0.000046
15 0.000052
15 0.000052
20 0.000057
20 0.000057
21 10 0.000048
21 10 0.000048
15 0.000054
15 0.000054
...
...
58 15 0.000433
58 15 0.000433
20 0.000557
20 0.000557
59 10 0.000362
59 10 0.000362
15 0.000471
15 0.000471
20 0.000609
20 0.000609
Name: premium_rate, Length: 120, dtype: float64
Name: premium_rate, Length: 120, dtype: float64


disc_rate_ann: Annual discount rates by duration as a pandas Series.
disc_rate_ann: Annual discount rates by duration as a pandas Series.


.. code-block::
.. code-block::


>>> Projection.disc_rate_ann
>>> Projection.disc_rate_ann
year
year
0 0.00000
0 0.00000
1 0.00555
1 0.00555
2 0.00684
2 0.00684
3 0.00788
3 0.00788
4 0.00866
4 0.00866


146 0.03025
146 0.03025
147 0.03033
147 0.03033
148 0.03041
148 0.03041
149 0.03049
149 0.03049
150 0.03056
150 0.03056
Name: disc_rate_ann, Length: 151, dtype: float64
Name: disc_rate_ann, Length: 151, dtype: float64


The Series is saved in the Excel file *disc_rate_ann.xlsx*
The Series is saved in the Excel file *disc_rate_ann.xlsx*
placed in the model folder.
placed in the model folder.
:attr:`disc_rate_ann` is created by
:attr:`disc_rate_ann` is created by
Projection's `new_pandas`_ method,
Projection's `new_pandas`_ method,
so that the Series is saved in the separate file.
so that the Series is saved in the separate file.
The Series has the injected attribute
The Series has the injected attribute
of ``_mx_dataclident``::
of ``_mx_dataclident``::


>>> Projection.disc_rate_ann._mx_dataclient
>>> Projection.disc_rate_ann._mx_dataclient
<PandasData path='disc_rate_ann.xlsx' filetype='excel'>
<PandasData path='disc_rate_ann.xlsx' filetype='excel'>


.. seealso::
.. seealso::


* :func:`disc_rate_mth`
* :func:`disc_rate_mth`
* :func:`disc_factors`
* :func:`disc_factors`


mort_table: Mortality table by age and duration as a DataFrame.
mort_table: Mortality table by age and duration as a DataFrame.
See *basic_term_sample.xlsx* included in this library
See *basic_term_sample.xlsx* included in this library
for how the sample mortality rates are created.
for how the sample mortality rates are created.


.. code-block::
.. code-block::


>>> Projection.mort_table
>>> Projection.mort_table
0 1 2 3 4 5
0 1 2 3 4 5
Age
Age
18 0.000231 0.000254 0.000280 0.000308 0.000338 0.000372
18 0.000231 0.000254 0.000280 0.000308 0.000338 0.000372
19 0.000235 0.000259 0.000285 0.000313 0.000345 0.000379
19 0.000235 0.000259 0.000285 0.000313 0.000345 0.000379
20 0.000240 0.000264 0.000290 0.000319 0.000351 0.000386
20 0.000240 0.000264 0.000290 0.000319 0.000351 0.000386
21 0.000245 0.000269 0.000296 0.000326 0.000359 0.000394
21 0.000245 0.000269 0.000296 0.000326 0.000359 0.000394
22 0.000250 0.000275 0.000303 0.000333 0.000367 0.000403
22 0.000250 0.000275 0.000303 0.000333 0.000367 0.000403
.. ... ... ... ... ... ...
.. ... ... ... ... ... ...
116 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
116 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
117 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
117 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
118 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
118 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
119 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
119 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
120 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
120 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000


[103 rows x 6 columns]
[103 rows x 6 columns]


The DataFrame is saved in the Excel file *mort_table.xlsx*
The DataFrame is saved in the Excel file *mort_table.xlsx*
placed in the model folder.
placed in the model folder.
:attr:`mort_table` is created by
:attr:`mort_table` is created by
Projection's `new_pandas`_ method,
Projection's `new_pandas`_ method,
so that the DataFrame is saved in the separate file.
so that the DataFrame is saved in the separate file.
The DataFrame has the injected attribute
The DataFrame has the injected attribute
of ``_mx_dataclident``::
of ``_mx_dataclident``::


>>> Projection.mort_table._mx_dataclient
>>> Projection.mort_table._mx_dataclient
<PandasData path='mort_table.xlsx' filetype='excel'>
<PandasData path='mort_table.xlsx' filetype='excel'>


.. seealso::
.. seealso::


* :func:`mort_rate`
* :func:`mort_rate`
* :func:`mort_rate_mth`
* :func:`mort_rate_mth`


np: The `numpy`_ module.
np: The `numpy`_ module.
pd: The `pandas`_ module.
pd: The `pandas`_ module.


.. _numpy:
.. _numpy:
https://numpy.org/
https://numpy.org/


.. _pandas:
.. _pandas:
https://pandas.pydata.org/
https://pandas.pydata.org/


.. _new_pandas:
.. _new_pandas:
https://docs.modelx.io/en/latest/reference/space/generated/modelx.core.space.UserSpace.new_pandas.html
https://docs.modelx.io/en/latest/reference/space/generated/modelx.core.space.UserSpace.new_pandas.html


"""
"""


from modelx.serialize.jsonvalues import *
from modelx.serialize.jsonvalues import *


_formula = None
_formula = None


_bases = []
_bases = []


_allow_none = None
_allow_none = None


_spaces = []
_spaces = []


# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Cells
# Cells


def age(t):
def age(t):
"""The attained age at time t.
"""The attained age at time t.


Defined as::
Defined as::


age_at_entry() + duration(t)
age_at_entry() + duration(t)


.. seealso::
.. seealso::


* :func:`age_at_entry`
* :func:`age_at_entry`
* :func:`duration`
* :func:`duration`


"""
"""
return age_at_entry() + duration(t)
return age_at_entry() + duration(t)




def age_at_entry():
def age_at_entry():
"""The age at entry of the model points
"""The age at entry of the model points


The ``age_at_entry`` column of the DataFrame returned by
The ``age_at_entry`` column of the DataFrame returned by
:func:`model_point`.
:func:`model_point`.
"""
"""
return model_point()["age_at_entry"]
return model_point()["age_at_entry"]




def claim_pp(t):
def claim_pp(t):
"""Claim per policy
"""Claim per policy


The claim amount per plicy. Defaults to :func:`sum_assured`.
The claim amount per plicy. Defaults to :func:`sum_assured`.
"""
"""
return sum_assured()
return sum_assured()




def claims(t):
def claims(t):
"""Claims
"""Claims


Claims during the period from ``t`` to ``t+1`` defined as::
Claims during the period from ``t`` to ``t+1`` defined as::


claim_pp(t) * pols_death(t)
claim_pp(t) * pols_death(t)


.. seealso::
.. seealso::


* :func:`claim_pp`
* :func:`claim_pp`
* :func:`pols_death`
* :func:`pols_death`


"""
"""
return claim_pp(t) * pols_death(t)
return claim_pp(t) * pols_death(t)




def commissions(t):
def commissions(t):
"""Commissions
"""Commissions


By default, 100% premiums for the first year, 0 otherwise.
By default, 100% premiums for the first year, 0 otherwise.


.. seealso::
.. seealso::


* :func:`premiums`
* :func:`premiums`
* :func:`duration`
* :func:`duration`


"""
"""
return (duration(t) == 0) * premiums(t)
return (duration(t) == 0) * premiums(t)




def disc_factors():
def disc_factors():
"""Discount factors.
"""Discount factors.


Vector of the discount factors as a Numpy array. Used for calculating
Vector of the discount factors as a Numpy array. Used for calculating
the present values of cashflows.
the present values of cashflows.


.. seealso::
.. seealso::


:func:`disc_rate_mth`
:func:`disc_rate_mth`
"""
"""
return np.array(list((1 + disc_rate_mth()[t])**(-t) for t in range(max_proj_len())))
return np.array(list((1 + disc_rate_mth()[t])**(-t) for t in range(max_proj_len())))




def disc_rate_mth():
def disc_rate_mth():
"""Monthly discount rate
"""Monthly discount rate


Nummpy array of monthly discount rates from time 0 to :func:`max_proj_len` - 1
Nummpy array of monthly discount rates from time 0 to :func:`max_proj_len` - 1
defined as::
defined as::


(1 + disc_rate_ann)**(1/12) - 1
(1 + disc_rate_ann)**(1/12) - 1


.. seealso::
.. seealso::


:func:`disc_rate_ann`
:func:`disc_rate_ann`


"""
"""
return np.array(list((1 + disc_rate_ann[t//12])**(1/12) - 1 for t in range(max_proj_len())))
disc = pd.Series(disc_rate_ann)
return np.array(list((1 + disc[t//12])**(1/12) - 1 for t in range(max_proj_len())))




def duration(t):
def duration(t):
"""Duration of model points at ``t`` in years
"""Duration of model points at ``t`` in years


.. seealso:: :func:`duration_mth`
.. seealso:: :func:`duration_mth`


"""
"""
return duration_mth(t) //12
return duration_mth(t) //12




def duration_mth(t):
def duration_mth(t):
"""Duration of model points at ``t`` in months
"""Duration of model points at ``t`` in months


Indicates how many months the policies have been in-force at ``t``.
Indicates how many months the policies have been in-force at ``t``.
The initial values at time 0 are read from the ``duration_mth`` column in
The initial values at time 0 are read from the ``duration_mth`` column in
:attr:`model_point_table` through :func:`model_point`.
:attr:`model_point_table` through :func:`model_point`.
Increments by 1 as ``t`` increments.
Increments by 1 as ``t`` increments.
Negative values of :func:`duration_mth` indicate future new business
Negative values of :func:`duration_mth` indicate future new business
policies. For example, If the :func:`duration_mth` is
policies. For example, If the :func:`duration_mth` is
-15 at time 0, the model point is issued at ``t=15``.
-15 at time 0, the model point is issued at ``t=15``.


.. seealso:: :func:`model_point`
.. seealso:: :func:`model_point`


"""
"""


if t == 0:
if t == 0:
return model_point()['duration_mth']
return model_point()['duration_mth']
else:
else:
return duration_mth(t-1) + 1
return duration_mth(t-1) + 1




def expense_acq():
def expense_acq():
"""Acquisition expense per policy
"""Acquisition expense per policy


``300`` by default.
``300`` by default.
"""
"""
return 300
return 300




def expense_maint():
def expense_maint():
"""Annual maintenance expense per policy
"""Annual maintenance expense per policy


``60`` by default.
``60`` by default.
"""
"""
return 60
return 60




def expenses(t):
def expenses(t):
"""Expenses
"""Expenses


Expenses during the period from ``t`` to ``t+1``
Expenses during the period from ``t`` to ``t+1``
defined as the sum of acquisition expenses and maintenance expenses.
defined as the sum of acquisition expenses and maintenance expenses.
The acquisition expenses are modeled as :func:`expense_acq`
The acquisition expenses are modeled as :func:`expense_acq`
times :func:`pols_new_biz`.
times :func:`pols_new_biz`.
The maintenance expenses are modeled as :func:`expense_maint`
The maintenance expenses are modeled as :func:`expense_maint`
times :func:`inflation_factor` times :func:`pols_if_at` before
times :func:`inflation_factor` times :func:`pols_if_at` before
decrement.
decrement.


.. seealso::
.. seealso::


* :func:`expense_acq`
* :func:`expense_acq`
* :func:`expense_maint`
* :func:`expense_maint`
* :func:`inflation_factor`
* :func:`inflation_factor`
* :func:`pols_new_biz`
* :func:`pols_new_biz`
* :func:`pols_if_at`
* :func:`pols_if_at`
"""
"""


return expense_acq() * pols_new_biz(t) \
return expense_acq() * pols_new_biz(t) \
+ pols_if_at(t, "BEF_DECR") * expense_maint()/12 * inflation_factor(t)
+ pols_if_at(t, "BEF_DECR") * expense_maint()/12 * inflation_factor(t)




def inflation_factor(t):
def inflation_factor(t):
"""The inflation factor at time t
"""The inflation factor at time t


.. seealso::
.. seealso::


* :func:`inflation_rate`
* :func:`inflation_rate`


"""
"""
return (1 + inflation_rate())**(t/12)
return (1 + inflation_rate())**(t/12)




def inflation_rate():
def inflation_rate():
"""Inflation rate"""
"""Inflation rate"""
return 0.01
return 0.01




def lapse_rate(t):
def lapse_rate(t):
"""Lapse rate
"""Lapse rate


By default, the lapse rate assumption is defined by duration as::
By default, the lapse rate assumption is defined by duration as::


max(0.1 - 0.02 * duration(t), 0.02)
max(0.1 - 0.02 * duration(t), 0.02)


.. seealso::
.. seealso::


:func:`duration`
:func:`duration`


"""
"""
return np.maximum(0.1 - 0.02 * duration(t), 0.02)
return np.maximum(0.1 - 0.02 * duration(t), 0.02)




def loading_prem():
def loading_prem():
"""Loading per premium
"""Loading per premium


.. note::
.. note::
This cells is not used by default.
This cells is not used by default.


``0.5`` by default.
``0.5`` by default.


.. seealso::
.. seealso::


* :func:`premium_pp`
* :func:`premium_pp`


"""
"""
return 0.5
return 0.5




max_proj_len = lambda: max(proj_len())
max_proj_len = lambda: int(max(proj_len()))
"""The max of all projection lengths
"""The max of all projection lengths


Defined as ``max(proj_len())``
Defined as ``max(proj_len())``


.. seealso::
.. seealso::
:func:`proj_len`
:func:`proj_len`
"""
"""


def model_point():
def model_point():
"""Target model points
"""Target model points


Returns as a DataFrame the model points to be in the scope of calculation.
Returns as a DataFrame the model points to be in the scope of calculation.
By default, this Cells returns the entire :attr:`model_point_table`
By default, this Cells returns the entire :attr:`model_point_table`
without change.
without change.
To select model points, change this formula so that this
To select model points, change this formula so that this
Cells returns a DataFrame that contains only the selected model points.
Cells returns a DataFrame that contains only the selected model points.


Examples:
Examples:
To select only the model point 1::
To select only the model point 1::


def model_point():
def model_point():
return model_point_table.loc[1:1]
return model_point_table.loc[1:1]


To select model points whose ages at entry are 40 or greater::
To select model points whose ages at entry are 40 or greater::


def model_point():
def model_point():
return model_point_table[model_point_table["age_at_entry"] >= 40]
return model_point_table[model_point_table["age_at_entry"] >= 40]


Note that the shape of the returned DataFrame must be the
Note that the shape of the returned DataFrame must be the
same as the original DataFrame, i.e. :attr:`model_point_table`.
same as the original DataFrame, i.e. :attr:`model_point_table`.


When selecting only one model point, make sure the
When selecting only one model point, make sure the
returned object is a DataFrame, not a Series, as seen in the example
returned object is a DataFrame, not a Series, as seen in the example
above where ``model_point_table.loc[1:1]`` is specified
above where ``model_point_table.loc[1:1]`` is specified
instead of ``model_point_table.loc[1]``.
instead of ``model_point_table.loc[1]``.


Be careful not to accidentally change the original table.
Be careful not to accidentally change the original table.
"""
"""
return model_point_table.loc[1:500000]
df = pd.DataFrame.from_pandas(model_point_table)

return df.loc[1:50000]




def mort_rate(t):
def mort_rate(t):
"""Mortality rate to be applied at time t
"""Mortality rate to be applied at time t


Returns a Series of the mortality rates to be applied at time t.
Returns a Series of the mortality rates to be applied at time t.
The index of the Series is ``point_id``,
The index of the Series is ``point_id``,
copied from :func:`model_point`.
copied from :func:`model_point`.


.. seealso::
.. seealso::


* :func:`mort_table_reindexed`
* :func:`mort_table_reindexed`
* :func:`mort_rate_mth`
* :func:`mort_rate_mth`
* :func:`model_point`
* :func:`model_point`


"""
"""


# mi is a MultiIndex whose values are
# mi is a MultiIndex whose values are
# pairs of age at t and duration at t capped at 5 for all the model points.
# pairs of age at t and duration at t capped at 5 for all the model points.


# ``mort_table_reindexed().reindex(mi, fill_value=0)`` returns
# ``mort_table_reindexed().reindex(mi, fill_value=0)`` returns
# a Series of mortality rates whose indexes match the MultiIndex values.
# a Series of mortality rates whose indexes match the MultiIndex values.
# The ``set_axis`` method replace the MultiIndex with ``point_id``
# The ``set_axis`` method replace the MultiIndex with ``point_id``


mi = pd.MultiIndex.from_arrays([age(t), np.minimum(duration(t), 5)])
df = pd.DataFrame(
return mort_table_reindexed().reindex(
{'age':age(t),'duration':np.minimum(duration(t), 5)})
mi, fill_value=0).set_axis(model_point().index, inplace=False)

mi = pd.MultiIndex.from_frame(df)
mt = mort_table_reindexed().reindex(mi) #, fill_value=0)
mt = mt.fillna(0)
mt.index = model_point().index
return mt




def mort_rate_mth(t):
def mort_rate_mth(t):
"""Monthly mortality rate to be applied at time t
"""Monthly mortality rate to be applied at time t


.. seealso::
.. seealso::


* :attr:`mort_table`
* :attr:`mort_table`
* :func:`mort_rate`
* :func:`mort_rate`


"""
"""
return 1-(1- mort_rate(t))**(1/12)
return 1-(1- mort_rate(t))**(1/12)




def mort_table_reindexed():
def mort_table_reindexed():
"""MultiIndexed mortality table
"""MultiIndexed mortality table


Returns a Series of mortlity rates reshaped from :attr:`mort_table`.
Returns a Series of mortlity rates reshaped from :attr:`mort_table`.
The returned Series is indexed by age and duration capped at 5.
The returned Series is indexed by age and duration capped at 5.


"""
"""
mt = pd.DataFrame(mort_table)
result = []
result = []
for col in mort_table.columns:
for col in mt.columns:
df = mort_table[[col]]
df = mt[[col]]
df = df.assign(Duration=int(col)).set_index('Duration', append=True)[col]
df = df.assign(Duration=int(col)).set_index('Duration', append=True)[col]
result.append(df)
result.append(df)


return pd.concat(result)
return pd.concat(result)




def net_cf(t):
def net_cf(t):
"""Net cashflow
"""Net cashflow


Net cashflow for the period from ``t`` to ``t+1`` defined as::
Net cashflow for the period from ``t`` to ``t+1`` defined as::


premiums(t) - claims(t) - expenses(t) - commissions(t)
premiums(t) - claims(t) - expenses(t) - commissions(t)


.. seealso::
.. seealso::


* :func:`premiums`
* :func:`premiums`
* :func:`claims`
* :func:`claims`
* :func:`expenses`
* :func:`expenses`
* :func:`commissions`
* :func:`commissions`


"""
"""
return premiums(t) - claims(t) - expenses(t) - commissions(t)
return premiums(t) - claims(t) - expenses(t) - commissions(t)




def net_premium_pp():
def net_premium_pp():
"""Net premium per policy
"""Net premium per policy


.. note::
.. note::
This cells is not used by default.
This cells is not used by default.


The net premium per policy is defined so that
The net premium per policy is defined so that
the present value of net premiums equates to the present value of
the present value of net premiums equates to the present value of
claims::
claims::


pv_claims() / pv_pols_if()
pv_claims() / pv_pols_if()


.. seealso::
.. seealso::


* :func:`pv_claims`
* :func:`pv_claims`
* :func:`pv_pols_if`
* :func:`pv_pols_if`


"""
"""
with np.errstate(divide='ignore', invalid='ignore'):
with np.errstate(divide='ignore', invalid='ignore'):
return np.nan_to_num(pv_claims() / pv_pols_if())
return np.nan_to_num(pv_claims() / pv_pols_if())




def policy_term():
def policy_term():
"""The policy term of the model points.
"""The policy term of the model points.


The ``policy_term`` column of the DataFrame returned by
The ``policy_term`` column of the DataFrame returned by
:func:`model_point`.
:func:`model_point`.
"""
"""
return model_point()["policy_term"]
return model_point()["policy_term"]




def pols_death(t):
def pols_death(t):
"""Number of death occurring at time t"""
"""Number of death occurring at time t"""
return pols_if_at(t, "BEF_DECR") * mort_rate_mth(t)
return pols_if_at(t, "BEF_DECR") * mort_rate_mth(t)




def pols_if(t):
def pols_if(t):
"""Number of policies in-force
"""Number of policies in-force


:func:`pols_if(t)<pols_if>` is an alias
:func:`pols_if(t)<pols_if>` is an alias
for :func:`pols_if_at(t, "BEF_MAT")<pols_if_at>`.
for :func:`pols_if_at(t, "BEF_MAT")<pols_if_at>`.


.. seealso::
.. seealso::
* :func:`pols_if_at`
* :func:`pols_if_at`


"""
"""
return pols_if_at(t, "BEF_MAT")
return pols_if_at(t, "BEF_MAT")




def pols_if_at(t, timing):
def pols_if_at(t, timing):
"""Number of policies in-force
"""Number of policies in-force


:func:`pols_if_at(t, timing)<pols_if_at>` calculates
:func:`pols_if_at(t, timing)<pols_if_at>` calculates
the number of policies in-force at time ``t``.
the number of policies in-force at time ``t``.
The second parameter ``timing`` takes a string value to
The second parameter ``timing`` takes a string value to
indicate the timing of in-force,
indicate the timing of in-force,
which is either
which is either
``"BEF_MAT"``, ``"BEF_NB"`` or ``"BEF_DECR"``.
``"BEF_MAT"``, ``"BEF_NB"`` or ``"BEF_DECR"``.


.. rubric:: BEF_MAT
.. rubric:: BEF_MAT


The number of policies in-force before maturity after lapse and death.
The number of policies in-force before maturity after lapse and death.
At time 0, the value is read from :func:`pols_if_init`.
At time 0, the value is read from :func:`pols_if_init`.
For time > 0, defined as::
For time > 0, defined as::


pols_if_at(t-1, "BEF_DECR") - pols_lapse(t-1) - pols_death(t-1)
pols_if_at(t-1, "BEF_DECR") - pols_lapse(t-1) - pols_death(t-1)


.. rubric:: BEF_NB
.. rubric:: BEF_NB


The number of policies in-force before new business after maturity.
The number of policies in-force before new business after maturity.
Defined as::
Defined as::


pols_if_at(t, "BEF_MAT") - pols_maturity(t)
pols_if_at(t, "BEF_MAT") - pols_maturity(t)


.. rubric:: BEF_DECR
.. rubric:: BEF_DECR


The number of policies in-force before lapse and death after new business.
The number of policies in-force before lapse and death after new business.
Defined as::
Defined as::


pols_if_at(t, "BEF_NB") + pols_new_biz(t)
pols_if_at(t, "BEF_NB") + pols_new_biz(t)


.. seealso::
.. seealso::
* :func:`pols_if_init`
* :func:`pols_if_init`
* :func:`pols_lapse`
* :func:`pols_lapse`
* :func:`pols_death`
* :func:`pols_death`
* :func:`pols_maturity`
* :func:`pols_maturity`
* :func:`pols_new_biz`
* :func:`pols_new_biz`
* :func:`pols_if`
* :func:`pols_if`


"""
"""
if timing == "BEF_MAT":
if timing == "BEF_MAT":


if t == 0:
if t == 0:
return pols_if_init()
return pols_if_init()
else:
else:
return pols_if_at(t-1, "BEF_DECR") - pols_lapse(t-1) - pols_death(t-1)
return pols_if_at(t-1, "BEF_DECR") - pols_lapse(t-1) - pols_death(t-1)


elif timing == "BEF_NB":
elif timing == "BEF_NB":


return pols_if_at(t, "BEF_MAT") - pols_maturity(t)
return pols_if_at(t, "BEF_MAT") - pols_maturity(t)


elif timing == "BEF_DECR":
elif timing == "BEF_DECR":


return pols_if_at(t, "BEF_NB") + pols_new_biz(t)
return pols_if_at(t, "BEF_NB") + pols_new_biz(t)


else:
else:
raise ValueError("invalid timing")
raise ValueError("invalid timing")




def pols_if_init():
def pols_if_init():
"""Initial number of policies in-force
"""Initial number of policies in-force


Number of in-force policies at time 0 referenced from
Number of in-force policies at time 0 referenced from
:func:`pols_if_at(0, "BEF_MAT")<pols_if_at>`.
:func:`pols_if_at(0, "BEF_MAT")<pols_if_at>`.
"""
"""
return model_point()["policy_count"].where(duration_mth(0) > 0, other=0)
return model_point()["policy_count"].where(duration_mth(0) > 0, other=0)




def pols_lapse(t):
def pols_lapse(t):
"""Number of lapse occurring at time t
"""Number of lapse occurring at time t


.. seealso::
.. seealso::
* :func:`pols_if_at`
* :func:`pols_if_at`
* :func:`lapse_rate`
* :func:`lapse_rate`


"""
"""
return (pols_if_at(t, "BEF_DECR") - pols_death(t)) * (1-(1 - lapse_rate(t))**(1/12))
return (pols_if_at(t, "BEF_DECR") - pols_death(t)) * (1-(1 - lapse_rate(t))**(1/12))




def pols_maturity(t):
def pols_maturity(t):
"""Number of maturing policies
"""Number of maturing policies


The policy maturity occurs when
The policy maturity occurs when
:func:`duration_mth` equals 12 times :func:`policy_term`.
:func:`duration_mth` equals 12 times :func:`policy_term`.
The amount is equal to :func:`pols_if_at(t, "BEF_MAT")<pols_if_at>`.
The amount is equal to :func:`pols_if_at(t, "BEF_MAT")<pols_if_at>`.


otherwise ``0``.
otherwise ``0``.
"""
"""
return (duration_mth(t) == policy_term() * 12) * pols_if_at(t, "BEF_MAT")
return (duration_mth(t) == policy_term() * 12) * pols_if_at(t, "BEF_MAT")




def pols_new_biz(t):
def pols_new_biz(t):
"""Number of new business policies
"""Number of new business policies


The number of new business policies.
The number of new business policies.
The value :func:`duration_mth(0)<duration_mth>`
The value :func:`duration_mth(0)<duration_mth>`
for the selected model point is read from the ``policy_count`` column in
for the selected model point is read from the ``policy_count`` column in
:func:`model_point`. If the value is 0 or negative,
:func:`model_point`. If the value is 0 or negative,
the model point is new business at t=0 or at t when
the model point is new business at t=0 or at t when
:func:`duration_mth(t)<duration_mth>` is 0, and the
:func:`duration_mth(t)<duration_mth>` is 0, and the
:func:`pols_new_biz(t)<pols_new_biz>` is read from the ``policy_count``
:func:`pols_new_biz(t)<pols_new_biz>` is read from the ``policy_count``
in :func:`model_point`.
in :func:`model_point`.


.. seealso::
.. seealso::
* :func:`model_point`
* :func:`model_point`


"""
"""
return model_point()['policy_count'].where(duration_mth(t) == 0, other=0)
return model_point()['policy_count'].where(duration_mth(t) == 0, other=0)




def premium_pp():
def premium_pp():
"""Monthly premium per policy
"""Monthly premium per policy


A Series of monthly premiums per policy for all the model points,
A Series of monthly premiums per policy for all the model points,
calculated as::
calculated as::


np.around(sum_assured() * prem_rates, 2)
np.around(sum_assured() * prem_rates, 2)


where the ``prem_rates`` is a Series of premium rates
where the ``prem_rates`` is a Series of premium rates
retrieved from :attr:`premium_table`.
retrieved from :attr:`premium_table`.


.. seealso::
.. seealso::


* :attr:`premium_table`
* :attr:`premium_table`
* :func:`model_point`
* :func:`model_point`
* :func:`age_at_entry`
* :func:`age_at_entry`
* :func:`policy_term`
* :func:`policy_term`


"""
"""


# mi is a MultiIndex whose values are
# mi is a MultiIndex whose values are
# pairs of issue ages and policy terms for all the model points.
# pairs of issue ages and policy terms for all the model points.


# ``premium_table.reindex(mi)`` returns
# ``premium_table.reindex(mi)`` returns
# a Series of premium rates whose indexes match the MultiIndex values.
# a Series of premium rates whose indexes match the MultiIndex values.
# The ``set_axis`` method replace the MultiIndex with ``point_id``
# The ``set_axis`` method replace the MultiIndex with ``point_id``


mi = pd.MultiIndex.from_arrays([age_at_entry(), policy_term()])
# mi = pd.MultiIndex.from_arrays([age_at_entry(), policy_term()])
prem_rates = premium_table.reindex(mi).set_axis(
df = pd.DataFrame(
model_point().index, inplace=False)
{'age_at_entry':age_at_entry(),'policy_term':policy_term()})
mi = pd.MultiIndex.from_frame(df)
prem_rates = pd.Series(premium_table).reindex(mi)
prem_rates.index = model_point().index
return np.around(sum_assured() * prem_rates, 2)
return np.around(sum_assured() * prem_rates, 2)




def premiums(t):
def premiums(t):
"""Premium income
"""Premium income


Premium income during the period from ``t`` to ``t+1`` defined as::
Premium income during the period from ``t`` to ``t+1`` defined as::


premium_pp() * pols_if_at(t, "BEF_DECR")
premium_pp() * pols_if_at(t, "BEF_DECR")


.. seealso::
.. seealso::


* :func:`premium_pp`
* :func:`premium_pp`
* :func:`pols_if_at`
* :func:`pols_if_at`


"""
"""
return premium_pp() * pols_if_at(t, "BEF_DECR")
return premium_pp() * pols_if_at(t, "BEF_DECR")




def proj_len():
def proj_len():
"""Projection length in months
"""Projection length in months


:func:`proj_len` returns how many months the projection
:func:`proj_len` returns how many months the projection
for each model point should be carried out
for each model point should be carried out
for all the model point. Defined as::
for all the model point. Defined as::


np.maximum(12 * policy_term() - duration_mth(0) + 1, 0)
np.maximum(12 * policy_term() - duration_mth(0) + 1, 0)


Since this model carries out projections for all the model points
Since this model carries out projections for all the model points
simultaneously, the projections are actually carried out
simultaneously, the projections are actually carried out
from 0 to :attr:`max_proj_len` for all the model points.
from 0 to :attr:`max_proj_len` for all the model points.


.. seealso::
.. seealso::


* :func:`policy_term`
* :func:`policy_term`
* :func:`duration_mth`
* :func:`duration_mth`
* :attr:`max_proj_len`
* :attr:`max_proj_len`


"""
"""
return np.maximum(12 * policy_term() - duration_mth(0) + 1, 0)
return np.maximum(12 * policy_term() - duration_mth(0) + 1, 0)




def pv_claims():
def pv_claims():
"""Present value of claims
"""Present value of claims


.. seealso::
.. seealso::


* :func:`claims`
* :func:`claims`


"""
"""
cl = np.array(list(claims(t) for t in range(max_proj_len()))).transpose()
cl = np.array(list(np.array(claims(t)) for t in range(max_proj_len()))).transpose()


return cl @ disc_factors()[:max_proj_len()]
return cl @ disc_factors()[:max_proj_len()]




def pv_commissions():
def pv_commissions():
"""Present value of commissions
"""Present value of commissions


.. seealso::
.. seealso::


* :func:`expenses`
* :func:`expenses`


"""
"""
result = np.array(list(commissions(t) for t in range(max_proj_len()))).transpose()
result = np.array(list(np.array(commissions(t)) for t in range(max_proj_len()))).transpose()


return result @ disc_factors()[:max_proj_len()]
return result @ disc_factors()[:max_proj_len()]




def pv_expenses():
def pv_expenses():
"""Present value of expenses
"""Present value of expenses


.. seealso::
.. seealso::


* :func:`expenses`
* :func:`expenses`


"""
"""
result = np.array(list(expenses(t) for t in range(max_proj_len()))).transpose()
result = np.array(list(np.array(expenses(t)) for t in range(max_proj_len()))).transpose()


return result @ disc_factors()[:max_proj_len()]
return result @ disc_factors()[:max_proj_len()]




def pv_net_cf():
def pv_net_cf():
"""Present value of net cashflows.
"""Present value of net cashflows.


Defined as::
Defined as::


pv_premiums() - pv_claims() - pv_expenses() - pv_commissions()
pv_premiums() - pv_claims() - pv_expenses() - pv_commissions()


.. seealso::
.. seealso::


* :func:`pv_premiums`
* :func:`pv_premiums`
* :func:`pv_claims`
* :func:`pv_claims`
* :func:`pv_expenses`
* :func:`pv_expenses`
* :func:`pv_commissions`
* :func:`pv_commissions`


"""
"""
return pv_premiums() - pv_claims() - pv_expenses() - pv_commissions()
return pv_premiums() - pv_claims() - pv_expenses() - pv_commissions()




def pv_pols_if():
def pv_pols_if():
"""Present value of policies in-force
"""Present value of policies in-force


.. note::
.. note::
This cells is not used by default.
This cells is not used by default.


The discounted sum of the number of in-force policies at each month.
The discounted sum of the number of in-force policies at each month.
It is used as the annuity factor for calculating :func:`net_premium_pp`.
It is used as the annuity factor for calculating :func:`net_premium_pp`.


"""
"""
result = np.array(list(pols_if_at(t, "BEF_DECR") for t in range(max_proj_len()))).transpose()
result = np.array(list(pols_if_at(t, "BEF_DECR") for t in range(max_proj_len()))).transpose()


return result @ disc_factors()[:max_proj_len()]
return result @ disc_factors()[:max_proj_len()]




def pv_premiums():
def pv_premiums():
"""Present value of premiums
"""Present value of premiums


.. seealso::
.. seealso::


* :func:`premiums`
* :func:`premiums`


"""
"""
result = np.array(list(premiums(t) for t in range(max_proj_len()))).transpose()
result = np.array(list(np.array(premiums(t)) for t in range(max_proj_len()))).transpose()


return result @ disc_factors()[:max_proj_len()]
return result @ disc_factors()[:max_proj_len()]




def result_cf():
def result_cf():
"""Result table of cashflows
"""Result table of cashflows


.. seealso::
.. seealso::


* :func:`premiums`
* :func:`premiums`
* :func:`claims`
* :func:`claims`
* :func:`expenses`
* :func:`expenses`
* :func:`commissions`
* :func:`commissions`
* :func:`net_cf`
* :func:`net_cf`


"""
"""


t_len = range(max_proj_len())
t_len = range(max_proj_len())


data = {
data = {
"Premiums": [sum(premiums(t)) for t in t_len],
"Premiums": [sum(premiums(t)) for t in t_len],
"Claims": [sum(claims(t)) for t in t_len],
"Claims": [sum(claims(t)) for t in t_len],
"Expenses": [sum(expenses(t)) for t in t_len],
"Expenses": [sum(expenses(t)) for t in t_len],
"Commissions": [sum(commissions(t)) for t in t_len],
"Commissions": [sum(commissions(t)) for t in t_len],
"Net Cashflow": [sum(net_cf(t)) for t in t_len]
"Net Cashflow": [sum(net_cf(t)) for t in t_len]
}
}


return pd.DataFrame(data, index=t_len)
return pd.DataFrame(data, index=t_len)




def result_pols():
def result_pols():
"""Result table of policy decrement
"""Result table of policy decrement


.. seealso::
.. seealso::


* :func:`pols_if`
* :func:`pols_if`
* :func:`pols_maturity`
* :func:`pols_maturity`
* :func:`pols_new_biz`
* :func:`pols_new_biz`
* :func:`pols_death`
* :func:`pols_death`
* :func:`pols_lapse`
* :func:`pols_lapse`


"""
"""


t_len = range(max_proj_len())
t_len = range(max_proj_len())


data = {
data = {
"pols_if": [sum(pols_if(t)) for t in t_len],
"pols_if": [sum(pols_if(t)) for t in t_len],
"pols_maturity": [sum(pols_maturity(t)) for t in t_len],
"pols_maturity": [sum(pols_maturity(t)) for t in t_len],
"pols_new_biz": [sum(pols_new_biz(t)) for t in t_len],
"pols_new_biz": [sum(pols_new_biz(t)) for t in t_len],
"pols_death": [sum(pols_death(t)) for t in t_len],
"pols_death": [sum(pols_death(t)) for t in t_len],
"pols_lapse": [sum(pols_lapse(t)) for t in t_len]
"pols_lapse": [sum(pols_lapse(t)) for t in t_len]
}
}


return pd.DataFrame(data, index=t_len)
return pd.DataFrame(data, index=t_len)




def result_pv():
def result_pv():
"""Result table of present value of cashflows
"""Result table of present value of cashflows


.. seealso::
.. seealso::


* :func:`pv_premiums`
* :func:`pv_premiums`
* :func:`pv_claims`
* :func:`pv_claims`
* :func:`pv_expenses`
* :func:`pv_expenses`
* :func:`pv_commissions`
* :func:`pv_commissions`
* :func:`pv_net_cf`
* :func:`pv_net_cf`


"""
"""




data = {
data = {
"PV Premiums": pv_premiums(),
"PV Premiums": pv_premiums(),
"PV Claims": pv_claims(),
"PV Claims": pv_claims(),
"PV Expenses": pv_expenses(),
"PV Expenses": pv_expenses(),
"PV Commissions": pv_commissions(),
"PV Commissions": pv_commissions(),
"PV Net Cashflow": pv_net_cf()
"PV Net Cashflow": pv_net_cf()
}
}


return pd.DataFrame(data, index=model_point().index)
return pd.DataFrame(data, index=model_point().index)




def sex():
def sex():
"""The sex of the model points
"""The sex of the model points


.. note::
.. note::
This cells is not used by default.
This cells is not used by default.


The ``sex`` column of the DataFrame returned by
The ``sex`` column of the DataFrame returned by
:func:`model_point`.
:func:`model_point`.
"""
"""
return model_point()["sex"]
return model_point()["sex"]




def sum_assured():
def sum_assured():
"""The sum assured of the model points
"""The sum assured of the model points


The ``sum_assured`` column of the DataFrame returned by
The ``sum_assured`` column of the DataFrame returned by
:func:`model_point`.
:func:`model_point`.
"""
"""
return model_point()["sum_assured"]
return model_point()["sum_assured"]




# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# References
# References


disc_rate_ann = ("DataSpec", 2338265319888, 2338257131216)
disc_rate_ann = ("DataSpec", 139635188956320, 139635194318416)


mort_table = ("DataSpec", 2338265487776, 2338257655360)
mort_table = ("DataSpec", 139635194252352, 139641319080096)

np = ("Module", "numpy")


pd = ("Module", "pandas")
premium_table = ("DataSpec", 139635194275920, 139635188955120)


premium_table = ("DataSpec", 2338265489072, 2338259026368)


model_point_table = ("DataSpec", 2338265488112, 2338265047344)