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
};