Exposure Calculation with ExperienceAnalysis.jl

JuliaActuary is an ecosystem of packages that makes Julia the easiest language to get started for actuarial workflows.
mortalitytables
exposures
experience-analysis
dataframes
tutorial

In this tutorial, we will walk through how to calculate exposures using the ExperienceAnalysis.jl package.

In summary, the package will help calculate the exposure periods given parameters about the kind of period and timepoints under consideration. This will return an array of tuples with a from and to date:

using ExperienceAnalysis
using Dates

issue = Date(2016, 7, 4)
termination = Date(2020, 1, 17)
basis = ExperienceAnalysis.Anniversary(Year(1))
exposure(basis, issue, termination)
4-element Vector{@NamedTuple{from::Date, to::Date, policy_timestep::Int64}}:
 (from = Date("2016-07-04"), to = Date("2017-07-03"), policy_timestep = 1)
 (from = Date("2017-07-04"), to = Date("2018-07-03"), policy_timestep = 2)
 (from = Date("2018-07-04"), to = Date("2019-07-03"), policy_timestep = 3)
 (from = Date("2019-07-04"), to = Date("2020-01-17"), policy_timestep = 4)

Available Exposure Basis

  • ExperienceAnalysis.Anniversary(period) will give exposures periods based on the first date
  • ExperienceAnalysis.Calendar(period) will follow calendar periods (e.g. month or year)
  • ExperienceAnalysis.AnniversaryCalendar(period,period) will split into the smaller of the calendar or policy anniversary period.

Where period is a Period Type from the Dates standard library.

Calculate exposures with exposures(basis,from,to,continue_exposure).

  • continue_exposures indicates whether the exposure should be extended through the full exposure period rather than terminate at the to date.

Full Example

We’ll start with this as our data:

using DataFrames
df = DataFrame(
    id=[1, 2, 3],
    issue=[Date(2016, 7, 4), Date(2016, 1, 1), Date(2016, 1, 1)],
    end_date=[Date(2020, 1, 17), Date(2018, 5, 4), Date(2020, 12, 31)],
    status=["Claim", "Lapse", "Inforce"]
)
3×4 DataFrame
Row id issue end_date status
Int64 Date Date String
1 1 2016-07-04 2020-01-17 Claim
2 2 2016-01-01 2018-05-04 Lapse
3 3 2016-01-01 2020-12-31 Inforce

Define the start and end of the study:

study_end = Date(2020, 6, 30)
study_start = Date(2018, 6, 30)
2018-06-30

Calculate the exposure by broadcasting the exposure function over the three arrays we are passing to it:

df.exposure = exposure.(
    ExperienceAnalysis.Anniversary(Year(1)),   # The basis for our exposures
    df.issue,                                  # The `from` date
    df.end_date,                               # the last observed date
    df.status .== "Claim";                        # a boolean vector indicating continuation
    study_start=study_start,
    study_end=study_end
)
3-element Vector{Vector{@NamedTuple{from::Date, to::Date, policy_timestep::Int64}}}:
 [(from = Date("2018-06-30"), to = Date("2018-07-03"), policy_timestep = 2), (from = Date("2018-07-04"), to = Date("2019-07-03"), policy_timestep = 3), (from = Date("2019-07-04"), to = Date("2020-07-03"), policy_timestep = 4)]
 []
 [(from = Date("2018-06-30"), to = Date("2018-12-31"), policy_timestep = 3), (from = Date("2019-01-01"), to = Date("2019-12-31"), policy_timestep = 4), (from = Date("2020-01-01"), to = Date("2020-06-30"), policy_timestep = 5)]

In our dataframe, we actually have a column that contains an array of tuples now, so to expand it so that each exposure period gets a row, we flatten the dataframe to get our exposures:

df = flatten(df, :exposure)
6×5 DataFrame
Row id issue end_date status exposure
Int64 Date Date String NamedTup…
1 1 2016-07-04 2020-01-17 Claim (from = Date("2018-06-30"), to = Date("2018-07-03"), policy_timestep = 2)
2 1 2016-07-04 2020-01-17 Claim (from = Date("2018-07-04"), to = Date("2019-07-03"), policy_timestep = 3)
3 1 2016-07-04 2020-01-17 Claim (from = Date("2019-07-04"), to = Date("2020-07-03"), policy_timestep = 4)
4 3 2016-01-01 2020-12-31 Inforce (from = Date("2018-06-30"), to = Date("2018-12-31"), policy_timestep = 3)
5 3 2016-01-01 2020-12-31 Inforce (from = Date("2019-01-01"), to = Date("2019-12-31"), policy_timestep = 4)
6 3 2016-01-01 2020-12-31 Inforce (from = Date("2020-01-01"), to = Date("2020-06-30"), policy_timestep = 5)

Exposure Fraction

This can be extended to calculate the decimal fraction of the year under different day count conventions, such as assuming 30/360 or Actual/365, etc. using the DayCounts.jl package.

using DayCounts

df.exposure_fraction = map(e -> yearfrac(e.from, e.to, DayCounts.Actual360()), df.exposure)
df[:, [:exposure, :exposure_fraction]]
6×2 DataFrame
Row exposure exposure_fraction
NamedTup… Float64
1 (from = Date("2018-06-30"), to = Date("2018-07-03"), policy_timestep = 2) 0.00833333
2 (from = Date("2018-07-04"), to = Date("2019-07-03"), policy_timestep = 3) 1.01111
3 (from = Date("2019-07-04"), to = Date("2020-07-03"), policy_timestep = 4) 1.01389
4 (from = Date("2018-06-30"), to = Date("2018-12-31"), policy_timestep = 3) 0.511111
5 (from = Date("2019-01-01"), to = Date("2019-12-31"), policy_timestep = 4) 1.01111
6 (from = Date("2020-01-01"), to = Date("2020-06-30"), policy_timestep = 5) 0.502778

Discussion and Questions

If you have other ideas or questions, feel free to also open an issue, or discuss on the community Zulip or Slack #actuary channel. We welcome all actuarial and related disciplines!

References