5.10. Step 10: Primary Education Fate Refined Model

5.10.1. Model Description

At this step we add the refined “fate” module for primary school outcome. It adjusts the overall probabilities from the base module to enter and graduate from school by a set of relative factors (odds ratios) by individual characteristics. In the current implemntation mother’s education is introduced as relative factor. The added module allows to easily expand the list of distinguished population groups. Users are also given a choice if and how the refined model is used. One option is calibrating the aggregate outcomes by sex and district to the base model for all years of birth. The second option calibrates the model just for one birth cohort from which onward the refined module is used. All simulated future trends are then driven entirely by composition effects.

5.10.2. The new Educ1BaseFate.mpp Module

This module allows to add relative factors (odds ratios) to the modeling of primary education outcomes. Currently mother’s education by sex is introduced. The relative factors are used to modify the base probabilities of school entry and school retention until graduation. Aggregated outcomes by district and sex are calibrated in order to match the base model’s outcome. This calibration is performed either (1) for all years of birth, or (2) just once. In the first case, aggregate outcomes by district and sex are identical, but the model picks different children entering and graduating school, accounting for the relative differences (odds ratios) by mother’s education. In the second case, the calibration is done for a selected year of birth. For all following cohorts, parameters are frozen and all trends result from composition effects due to the changing educational composition of mothers. The module only affects persons born in the simulation as mother’s education is unknown for others.

Parameters:

  • Model selection: the 3 choices are (1) Use Base Model, (2) Use the refined model calibrated for all years of birth, (3) Use the refined model calibrated once.
  • Odds for starting primary education by mother’s education
  • Odds for graduating from primary education by mother’s education
  • The first birth cohort from which onwards the refined model is used, which can be any year beginning from the starting year of the simulation.

The module can be added or removed from the model without requiring modification in other modules. The only exception is the requirement to initialize mother’s education in the Start() function in PersonCore.mpp.

In its core, this module consists of two functions.

  • The function SetEduc1AdjustmentFactors() calculates the required adjustment factors (in the form of log odds by district and sex) to be applied in addition to the relative factors in order to get probabilities further broken down by mother’s education. The calculations are performed once at the end of each relevant calendar year as at this point in time the educational composition of mothers of all born in this year is known. Adjustment factors are either calculated once or updated each year according to the user’s model selection.
  • The function AdjustEducOne() applies the adjustments at the Person level at the first end of the year after birth. Thereby this module modifies the three “fate” states introduced in the base model, namely the two individual progression probabilities educ_one_prob1 and educ_one_prob2, as well as random outcome educ_one_fate based on the new probabilities.

How to change or expand the list of relative factors

  • The list of population groups is declared in the classification EDUC1_GROUP. The classification can be changed or expanded whereby a person can belong only to one of the groups. (For example, when adding an additional dimension ethnicity, all combinations of ethnicity and mother’s education have to be listed)
  • The individual group membership is a derived state educ1_group. When changing the levels of EDUC1_GROUP, the calculation of the derived state has to be updated accordingly.
  • No other code changes are required


////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
////////////////////////////////////////////////////////////////////////////////////////////////////

actor_set Person asSimBornAge0[sex][district_birth][educ1_group]
filter is_alive && person_type==PT_CHILD && integer_age==0;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
////////////////////////////////////////////////////////////////////////////////////////////////////

classification EDUC1_GROUP          //EN Population Groups
{
    E1G_00,                         //EN Mothers education low
    E1G_01,                         //EN Mothers education medium
    E1G_02                          //EN Mothers education high
};

classification EDUC1_MODEL          //EN Primary Education Model Selection
{
    E1M_BASE,                       //EN Use base model
    E1M_REFINED_ALIGNALL,           //EN Use refined model aligned to base for all birth cohorts
    E1M_REFINED_ALIGNONCE           //EN Use refined model aligned to base once
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
////////////////////////////////////////////////////////////////////////////////////////////////////

parameters
{
    double      Educ1StartOdds[EDUC1_GROUP][SEX];   //EN Odds of starting primary school
    double      Educ1GradOdds[EDUC1_GROUP][SEX];    //EN Odds primary school graduation
    EDUC1_MODEL Educ1Model;                         //EN Model Selection
    int         Educ1FirstCohortRefinedModel;       //EN First birth cohort to apply refined model
};

parameter_group PG_SchoolOneFateRefined             //EN Primary School Fate - Refined Model
{
    Educ1StartOdds, Educ1GradOdds,
    Educ1FirstCohortRefinedModel
};

parameter_group PG_SchoolOneFate                    //EN Primary School Fate
{
    Educ1Model,
    PG_SchoolOneFateBase,
    PG_SchoolOneFateRefined
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor states and functions
////////////////////////////////////////////////////////////////////////////////////////////////////

actor Globals                                       //EN Actor Globals
{
    double alignment_educ1_medium[SEX][DISTRICT_INT];    //EN Education alignment low to medium
    double alignment_educ1_high[SEX][DISTRICT_INT];      //EN Education alignment mdium to high
};

actor Clock
{
    void    SetEduc1AdjustmentFactors();             //EN Function calculating calibration factors
    hook    SetEduc1AdjustmentFactors, EndYearClock; //EN Hook to clock yearend

    double  AdjustedProbability(double dProb, double dLogOddEduc, double dLogOddAdjust);
};

actor Person
{
    EDUC_ONE_LEVEL  educ_mother = { EOL_LOW };      //EN Mothers education

    //EN Education risk group
    EDUC1_GROUP     educ1_group = ( educ_mother == EOL_LOW ) ?  E1G_00 :
                                  ( educ_mother == EOL_MEDIUM ) ?  E1G_01 : E1G_02;

    void        AdjustEducOne();
    hook        AdjustEducOne, YearEnd;

};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////////////////////////

void Clock::SetEduc1AdjustmentFactors()
{
    if ( clock_year >= MIN(SIM_YEAR_RANGE)  && (
       (Educ1Model == E1M_REFINED_ALIGNALL  && clock_year >= Educ1FirstCohortRefinedModel ) ||
       (Educ1Model == E1M_REFINED_ALIGNONCE && clock_year == Educ1FirstCohortRefinedModel )))
    {
        for (int nSex = 0; nSex < SIZE(SEX); nSex++ )
        {
            for (int nDist = 0; nDist < SIZE(DISTRICT_INT); nDist++ )
            {
                // calculate total population for given district and sex
                double nTotalPop = 0.0;
                for ( int dGroup = 0; dGroup < SIZE(EDUC1_GROUP); dGroup++ )
                {
                    nTotalPop = nTotalPop + asSimBornAge0[nSex][nDist][dGroup]->Count();
                }

                // Run Adjustment for school entry
                double dFactorEntry = 0.0;
                if ( nTotalPop > 0.0 )
                {
                    int nIterations = 0;
                    double dResultProb = 10.0;
                    double dTargetProb = StartPrimaryDistrict[nSex][RANGE_POS(YOB_START_PRIMARY,clock_year)][nDist];
                    double dLower = -10.0;
                    double dUpper = 10.0;
                    double dCenter = 0.0;
                    while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
                    {
                        nIterations++;
                        dCenter = (dLower + dUpper) / 2.0;
                        dResultProb = 0.0;
                        for ( int nGroup = 0; nGroup < SIZE(EDUC1_GROUP); nGroup++ )
                        {
                            dResultProb = dResultProb + ( asSimBornAge0[nSex][nDist][nGroup]->Count() / nTotalPop ) *
                                AdjustedProbability(dTargetProb, log(Educ1StartOdds[nGroup][nSex]), dCenter);
                        }
                        if (dTargetProb > dResultProb) dLower = dCenter;
                        else dUpper = dCenter;
                    }
                    dFactorEntry = dCenter;
                }
                // set factor
                asGlobals->Item(0)->alignment_educ1_medium[nSex][nDist] = dFactorEntry;

                // Run Adjustment for school retention
                double dFactorGrad = 0.0;
                if ( nTotalPop > 0.0 )
                {
                    int nIterations = 0;
                    double dResultProb = 10.0;
                    double dTargetProb = GradPrimaryDistrict[nSex][RANGE_POS(YOB_GRAD_PRIMARY,clock_year)][nDist];
                    double dLower = -10.0;
                    double dUpper = 10.0;
                    double dCenter = 0.0;
                    while (abs(dResultProb - dTargetProb) > 0.0001 && nIterations < 10000)
                    {
                        nIterations++;
                        dCenter = (dLower + dUpper) / 2.0;
                        dResultProb = 0.0;
                        for ( int nGroup = 0; nGroup < SIZE(EDUC1_GROUP); nGroup++ )
                        {
                            dResultProb = dResultProb + ( asSimBornAge0[nSex][nDist][nGroup]->Count() / nTotalPop ) *
                                AdjustedProbability(dTargetProb, log(Educ1GradOdds[nGroup][nSex]), dCenter);
                        }
                        if (dTargetProb > dResultProb) dLower = dCenter;
                        else dUpper = dCenter;
                    }
                    dFactorGrad = dCenter;
                }
                // set factor
                asGlobals->Item(0)->alignment_educ1_high[nSex][nDist] = dFactorGrad;
            }
        }
    }
}

double Clock::AdjustedProbability(double dProb, double dLogOddEduc, double dLogOddAdjust)
{
    if (dProb >= 0.9999) dProb = 0.9999;
    double dExp = exp(log(dProb / (1 - dProb)) + dLogOddEduc + dLogOddAdjust);
    return dExp / (1 + dExp);
}

void Person::AdjustEducOne()
{
    // Adjustment for selection of  refined model aligned to base for all birth cohorts
    if ( person_type == PT_CHILD && integer_age == 0 && calendar_year >= MIN(SIM_YEAR_RANGE)  &&
        calendar_year >= Educ1FirstCohortRefinedModel && Educ1Model == E1M_REFINED_ALIGNALL )
    {
        educ_one_prob1 = lClock->AdjustedProbability(
            educ_one_prob1,
            log(Educ1StartOdds[educ1_group][sex]),
            asGlobals->Item(0)->alignment_educ1_medium[sex][district_birth]);
        educ_one_prob2 = lClock->AdjustedProbability(
            educ_one_prob2,
            log(Educ1GradOdds[educ1_group][sex]),
            asGlobals->Item(0)->alignment_educ1_high[sex][district_birth]);
        EDUC_ONE_LEVEL eolFate = EOL_LOW;
        if ( RandUniform(22) < educ_one_prob1 ) eolFate = EOL_MEDIUM;
        if ( eolFate == EOL_MEDIUM && RandUniform(23) < educ_one_prob2 )  eolFate = EOL_HIGH;
        educ_one_fate = eolFate;
    }
    else if ( person_type == PT_CHILD && integer_age == 0 && calendar_year >= MIN(SIM_YEAR_RANGE)  &&
        calendar_year >= Educ1FirstCohortRefinedModel && Educ1Model == E1M_REFINED_ALIGNONCE )
    {
        educ_one_prob1 = lClock->AdjustedProbability(
            StartPrimaryDistrict[sex][RANGE_POS(YOB_START_PRIMARY,Educ1FirstCohortRefinedModel)][district_birth],
            log(Educ1StartOdds[educ1_group][sex]),
            asGlobals->Item(0)->alignment_educ1_medium[sex][district_birth]);
        educ_one_prob2 = lClock->AdjustedProbability(
            GradPrimaryDistrict[sex][RANGE_POS(YOB_GRAD_PRIMARY,Educ1FirstCohortRefinedModel)][district_birth],
            log(Educ1GradOdds[educ1_group][sex]),
            asGlobals->Item(0)->alignment_educ1_high[sex][district_birth]);
        EDUC_ONE_LEVEL eolFate = EOL_LOW;
        if ( RandUniform(22) < educ_one_prob1 ) eolFate = EOL_MEDIUM;
        if ( eolFate == EOL_MEDIUM && RandUniform(23) < educ_one_prob2 )  eolFate = EOL_HIGH;
        educ_one_fate = eolFate;
    }
}

5.10.3. Initializations in the Start() Function

The state educ_mother is added to the list of variables initiated in the Start() Function.


    else if (person_type == PT_CHILD) // Person born in simulation
    {
        // (A) States corresponding to Starting population file variables
        if (RandUniform(4) < 100.0 / (100.0 + SexRatio[RANGE_POS(SIM_YEAR_RANGE, calendar_year)]))
        {
            sex  = FEMALE;
        }
        else sex = MALE;
        time                = pePers->time;
        family_role         = FR_CHILD;
        district_int        = pePers->district_int;
        district_birth      = district_int;

        // (B) Other states
        time_of_birth       = time;
        calendar_year       = (int)time_of_birth;
        ever_resident       = is_resident;
        educ_mother         = pePers->educ_one_fate;