Step 7: Refined Mortality
Model Description
At this step we add the refined mortality module accounting for life expectancy differentials by educational attainment. The module is parameterized by period life expectancies at age 30 and age 65. The module automatically calibrates the period mortality rates of the base module in order to meet the target parameters of life expectancy. Additionally, the outcome can be ‘aligned back’ to the original overall mortality rates keeping the relative differences in risks between education groups.
The new MortalityByEducation.mpp Module
This module implements differential mortality by population “mortality groups”. While the grouping is generic, in the current implementation it refers to educational attainment, but the list can easily be extended or changed. The module is optional, and the user can choose between various options:
Disable the module: the overall mortality rates of the base model apply and this module is switched off
Model without alignment: The base rates are adjusted to meet life expectancy parameters by population group. Parameters are the remaining period life expectancy at age 30 and age 65 by sex. This adjustment is made in the pre-simulation phase where new parameter tables are generated.
Model with alignment: after the initial calibration of rates to meet life expectancy targets by group, the resuling rates are proportionally scaled back in order to keep the overall mortality rates of the base model while accounting for the relative mortality differences by population group. This alignment is performed in yearly intervals during the simulation accounting for the changing population composition by risk group.
How to change or add the population groups for relative risks?
The list of population groups for which relative risks are applied is generic and can be modified and extended easily.
The classification CHILD_MORTALITY_GROUP lists all population groups distinguished.
The state mortality_group contains the individual group membership and has to be adapted if the list of categories is modified.
Besides these two changes no coding is required. The calibration routines automatically adjust the hazards according to the population composition by group membership.
////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
////////////////////////////////////////////////////////////////////////////////////////////////////
classification SELECTED_MORTALITY_MODEL //EN Mortality Model Options
{
SMM_MACRO, //EN Disable child mortality model
SMM_MICRO_NOT_ALIGNED, //EN Child mortality model without alignment
SMM_MICRO_ALIGNED //EN Aligned to overall mortality rates
};
classification MORTALITY_GROUP //EN Population group for mortality
{
MG_00, //EN Low education
MG_01, //EN Medium education
MG_02 //EN High education
};
classification LIFE_EXPECT //EN Life Expectancy Parameters
{
LE_30, //EN Life expectancy at 30
LE_65 //EN Life expectancy at 65
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
////////////////////////////////////////////////////////////////////////////////////////////////////
parameters
{
//EN Child mortality model selection
SELECTED_MORTALITY_MODEL SelectedMortalityModel;
//EN Period life expectancy
double LifeExpectancy[SEX][LIFE_EXPECT][SIM_YEAR_RANGE][MORTALITY_GROUP];
//EN Mortality Trend
model_generated double MortalityByGroup[SEX][MORTALITY_GROUP][AGE_RANGE][SIM_YEAR_RANGE];
};
parameter_group PG02_RefinedMortality //EN Mortality by Education
{
LifeExpectancy
};
parameter_group PG_Mortality //EN Mortality
{
SelectedMortalityModel,
PG02_OverallMortality,
PG02_RefinedMortality
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Globals
////////////////////////////////////////////////////////////////////////////////////////////////////
actor Globals
{
void MortalityCalibration(); //EN Mortality calibration
double CurrentAlignedMortality[MORTALITY_GROUP][AGE_RANGE][SEX];
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Clock
////////////////////////////////////////////////////////////////////////////////////////////////////
actor Clock
{
void CallMortalityCalibration(); //EN Call mortality calibration event
hook CallMortalityCalibration, StartYearClock;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Person
////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Child mortality risk group
MORTALITY_GROUP mortality_group = (educ_fate == EL3_LOW) ? MG_00 :
(educ_fate == EL3_MEDIUM) ? MG_01 : MG_02;
// Current aligned mortality
double current_aligned_mortality = { 0.0 };
//EN Activates Module at birth if selected
void ActivateRefinedMortalityModule(); hook ActivateRefinedMortalityModule, Start;
event timeRefinedMortalityEvent, RefinedMortalityEvent; //EN Mortality Event
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Person functions implementation
////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::ActivateRefinedMortalityModule()
{
if ( SelectedMortalityModel != SMM_MACRO) use_base_mortality = FALSE;
}
TIME Person::timeRefinedMortalityEvent()
{
double dEventTime = TIME_INFINITE;
if ( !use_base_mortality && in_projected_time && ever_resident)
{
double dHazard = MortalityByGroup[sex][mortality_group][integer_age]
[RANGE_POS(SIM_YEAR_RANGE, calendar_year)];
// aligned model
if (SelectedMortalityModel == SMM_MICRO_ALIGNED)
{
dHazard = current_aligned_mortality;
}
// if hazard is positive calculate event time
if (dHazard > 0) dEventTime = WAIT(-log(RandUniform(15)) / dHazard);
}
// return the event time, if the maximum age is not reached at that point
if (dEventTime < time_of_birth + MAX(AGE_RANGE) + 1.0) return dEventTime;
// otherwise, return the moment, at which the maximum age is reached
else if (!use_base_mortality) return time_of_birth + MAX(AGE_RANGE) + 0.9999;
else return TIME_INFINITE;
}
void Person::RefinedMortalityEvent()
{
MortalityEvent();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Clock functions implementation
////////////////////////////////////////////////////////////////////////////////////////////////////
void Clock::CallMortalityCalibration()
{
if (clock_year >= MIN(SIM_YEAR_RANGE) && SelectedMortalityModel == SMM_MICRO_ALIGNED)
{
asGlobals->Item(0)->MortalityCalibration();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Globals functions implementation
////////////////////////////////////////////////////////////////////////////////////////////////////
void Globals::MortalityCalibration()
{
// local matrices
double dDeaths[SIZE(AGE_RANGE)][SIZE(SEX)]; // Number expected deaths
double dProb[SIZE(AGE_RANGE)][SIZE(SEX)]; // Death probability
long nPop[SIZE(AGE_RANGE)][SIZE(SEX)][SIZE(MORTALITY_GROUP)]; // Pop sizes
double dCalibrator[SIZE(AGE_RANGE)][SIZE(SEX)]; // Baseline Hazard in simulation
int nYear = asClock->Item(0)->clock_year - MIN(SIM_YEAR_RANGE); // Current Yyear since start
// Initialize matrices
// Set population sizes nPop and expected deaths nDeaths to 0
// sets death probabilities dProb by age and sex for the year in which the model is calibrated
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
dProb[nAge][nSex] = 1.0 - exp(-MortalityTable[nSex][nAge][nYear]);
dDeaths[nAge][nSex] = 0.0;
for (int nGroup = 0; nGroup < SIZE(MORTALITY_GROUP); nGroup++)
{
nPop[nAge][nSex][nGroup] = 0.0;
}
}
}
// Population Count
// calculates population sizes nPop by age, sex, risk group
// calculates expected deaths dDeaths by age and sex
for (long nJ = 0; nJ < asAllPersons->Count(); nJ++)
{
auto prPerson = asAllPersons->Item(nJ); //NOTE: to be changed to residents if migration is modeled
dDeaths[prPerson->integer_age][prPerson->sex] = dDeaths[prPerson->integer_age][prPerson->sex]
+ dProb[prPerson->integer_age][prPerson->sex];
nPop[prPerson->integer_age][prPerson->sex][prPerson->mortality_group]++;
}
// find calibrated baselines
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
double dUpper = 2.0;
double dLower = 0.0;
double dCenter = 1.0;
double dNumberDeaths = 0.0;
int nIterations = 10000;
while (abs(dNumberDeaths - dDeaths[nAge][nSex]) > 0.0001 && nIterations > 0)
{
nIterations--;
dCalibrator[nAge][nSex] = (dLower + dUpper) / 2.0;
dNumberDeaths = 0.0;
//Calculate numer of deaths for given dCalibrator
for (int nGroup = 0; nGroup < SIZE(MORTALITY_GROUP); nGroup++)
{
dNumberDeaths = dNumberDeaths + nPop[nAge][nSex][nGroup]
* (1 - exp(-dCalibrator[nAge][nSex] * MortalityByGroup[nSex][nGroup][nAge][nYear]));
}
// shrink search interval
if (dNumberDeaths > dDeaths[nAge][nSex]) dUpper = dCalibrator[nAge][nSex];
else dLower = dCalibrator[nAge][nSex];
}
}
}
// set global mortality by group, age and sex
for (int nAge = 0; nAge < SIZE(AGE_RANGE); nAge++)
{
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nGroup = 0; nGroup < SIZE(MORTALITY_GROUP); nGroup++)
{
CurrentAlignedMortality[nGroup][nAge][nSex]
= MortalityByGroup[nSex][nGroup][nAge][nYear] * dCalibrator[nAge][nSex];
}
}
}
for (long nJ = 0; nJ < asAllPersons->Count(); nJ++)
{
auto prPerson = asAllPersons->Item(nJ); //NOTE: to be changed to residents if migration is modeled
prPerson->current_aligned_mortality
= CurrentAlignedMortality[prPerson->mortality_group][prPerson->integer_age][prPerson->sex];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-Simulation
////////////////////////////////////////////////////////////////////////////////////////////////////
void PreSimulation()
{
// In the pre-simulation phase mortality rates by population group are found by binary search.
// For each simulated year, the overall mortality table is adjusted to obtain target life
// expectancies at age 65 and at age 30 for each distinguished population group.
// Local variables
double dTarget, dCenter, dResult, dAlive, dDeaths, dLower, dUpper;
int nIterations;
if (SelectedMortalityModel != SMM_MACRO)
{
// Find trend factors to fit the life expectancy at 65
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR_RANGE); nYear++)
{
for (int nGroup = 0; nGroup < SIZE(MORTALITY_GROUP); nGroup++)
{
dTarget = LifeExpectancy[nSex][LE_65][nYear][nGroup]; // Target life expectancy
dResult = 0.0; // Search result: life expectancy
dLower = 0.1; // Lower limit of calibration factor
dUpper = 10.0; // Upper limit of calibration factor
nIterations = 100000; // Maximal iterations
while (abs(dResult - dTarget) > 0.0001 && nIterations > 0)
{
nIterations--;
dCenter = (dLower + dUpper) / 2.0; // New calibration factor for probing
dResult = 0.0;
dAlive = 1.0; // Proportion of people still alive
// Life expectancy calculated applying calibration factor
for (int nAge = 65; nAge < SIZE(AGE_RANGE); nAge++)
{
// proportion of deaths in year: survival = exp(-hazard)
dDeaths = dAlive * (1 - exp(-MortalityTable[nSex][nAge][nYear] * dCenter));
dAlive = dAlive - dDeaths;
// people dying in this year are assumed to die in the middle of the year
dResult = dResult + dDeaths * 0.5 + dAlive;
}
// Moving the search limits for next iteration
if (dTarget < dResult) dLower = dCenter;
else dUpper = dCenter;
}
// applying the best solution to the mortality parameter
for (int nAge = 65; nAge < SIZE(AGE_RANGE); nAge++)
{
MortalityByGroup[nSex][nGroup][nAge][nYear] = dCenter * MortalityTable[nSex][nAge][nYear];
}
}
}
}
// Find trend factors to fit the life expectancy at 30 (while keeping the trend for 65+)
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR_RANGE); nYear++)
{
for (int nGroup = 0; nGroup < SIZE(MORTALITY_GROUP); nGroup++)
{
dTarget = LifeExpectancy[nSex][LE_30][nYear][nGroup]; // Target life expectancy
dResult = 0.0; // Search result: life expectancy
dLower = 0.1; // Lower limit of calibration factor
dUpper = 10.0; // Upper limit of calibration factor
nIterations = 100000; // Maximal iterations
while (abs(dResult - dTarget) > 0.0001 && nIterations > 0)
{
nIterations--;
dCenter = (dLower + dUpper) / 2.0; // New calibration factor for probing
dResult = 0.0;
dAlive = 1.0; // Proportion of people still alive
// Life expectancy calculated applying calibration factor
for (int nAge = 30; nAge < SIZE(AGE_RANGE); nAge++)
{
// proportion of deaths in year: survival = exp(-hazard)
if (nAge < 65) // Apply searched adjustment factor only for ages below 65
{
dDeaths = dAlive * (1 - exp(-MortalityTable[nSex][nAge][nYear] * dCenter));
}
else // apply known factor for ages 65+
{
dDeaths = dAlive * (1 - exp(-MortalityByGroup[nSex][nGroup][nAge][nYear]));
}
dAlive = dAlive - dDeaths;
// people dying in this year are assumed to die in the middle of the year
dResult = dResult + dDeaths * 0.5 + dAlive;
}
// Moving the search limits for next iteration
if (dTarget < dResult) dLower = dCenter;
else dUpper = dCenter;
}
// applying the best solution to the mortality parameter
for (int nAge = 30; nAge < 65; nAge++)
{
MortalityByGroup[nSex][nGroup][nAge][nYear] = dCenter * MortalityTable[nSex][nAge][nYear];
}
}
}
}
// Copy over parameters for age < 30
for (int nSex = 0; nSex < SIZE(SEX); nSex++)
{
for (int nYear = 0; nYear < SIZE(SIM_YEAR_RANGE); nYear++)
{
for (int nAge = 0; nAge < 30; nAge++)
{
for (int nGroup = 0; nGroup < SIZE(MORTALITY_GROUP); nGroup++)
{
MortalityByGroup[nSex][nGroup][nAge][nYear] = MortalityTable[nSex][nAge][nYear];
}
}
}
}
}
}
table Person CalibratedMortality //EN CalibratedMortality
{
sex + *
{
max_value_out(current_aligned_mortality)
}
*integer_age
*calendar_year
};