5.13. Step 13: Refined Fertility¶
5.13.1. Model Description¶
At this step we add a refined module for fertility which goes beyond age-specific rates by accounting for education, union formation, and parity. The new modules can replace or compliment the base model, in the latter case the aggregate outcomes of the refined model are aligned to the outcome of the base model.
5.13.2. The new FertilityRefinedEducUnionParity.mpp Module¶
This module implements detailed fertility models accounting for parity, geographical location, time since previous birth, partnership status, and an extendable list of other characteristics (currently grouping women by primary education outcome). Users can choose to align the aggregated results to the base model, either for total outcome (number of births) or births by age.
The module has four parameters:
- Model selection: 4 options, base model, refined model, refined model aligned to number of births of base model, refined model aligned to number of births by age of base model.
- First Births Rates by population group (currently: education), location (currently: region), partnership status, age.
- Higher order births: hazard regression parameters with a duration baseline (age; time since last birth) and relative risks for education and age group.
- Birth trends: an additional relative risk by calendar year and parity.
Simulated birth events only occure to residents in the projected time, as biographic information on parity and the most recent time of birth are contained in the starting population. As for immigrants no biographic information is available, immigrant women sample parity and timing of last birth from the female resident population of same education and partnership status and district. The function GetImmigrantParityTimeBirth() is hooked to the ImmigrationEvent().
This module can be added or removed from the model without any code change to other modules. It just requires the initialization of the states parity and time_since_last_birth_start based on the starting population file; this is done in the Start() function.
////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor Sets
////////////////////////////////////////////////////////////////////////////////////////////////////
//EN Potential mothers
actor_set Person asPotentialMothers filter is_potential_mother;
//EN Potential mothers by age
actor_set Person asPotentialMothersByAge[fertile_age] filter is_potential_mother;
//EN Female residents by age, district of residence and education
actor_set Person asWomenAgeEducDist[integer_age][educ_one_fate][district_nat]
filter is_alive && is_resident && sex==FEMALE;
////////////////////////////////////////////////////////////////////////////////////////////////////
// Types
////////////////////////////////////////////////////////////////////////////////////////////////////
classification BIRTH1_GROUP //EN Population Groups
{
B1G_00, //EN Never entered school
B1G_01, //EN Primary dropout
B1G_02 //EN Primary graduate
};
classification BIRTH1_LOC //EN Geographical Location
{
B1L_00, //EN Far-West
B1L_01, //EN West
B1L_02, //EN Central
B1L_03, //EN The Capital
B1L_04 //EN East
};
range PARITY_RANGE1 { 1, 15 }; //EN Parity range 1+
range PARITY_RANGE2 { 2, 15 }; //EN Parity range 2+
classification SELECTED_FERTILITY_MODEL //EN Fertility model options
{
SFM_MACRO, //EN Macro model (age only)
SFM_MICRO, //EN Micro model un-aligned
SFM_ALIGNE_BIRTHS, //EN Micro model with aligned number of births
SFM_ALIGNE_AGE //EN Micro model with aligned number of births and age
};
classification UNION_STATUS //EN Union Status
{
US_NO, //EN Never entered a union
US_YES //EN Entered a union
};
classification HIGHER_BIRTHS_PARA //EN Parameters for higher order births
{
HBP_PERIOD1, //EN 0-1 years after previous birth
HBP_PERIOD2, //EN 1-3 years after previous birth
HBP_PERIOD3, //EN 3-6 years after previous birth
HBP_PERIOD4, //EN 6-9 years after previous birth
HBP_PERIOD5, //EN 9-12 years after previous birth
HBP_PERIOD6, //EN 12+ years after previous birth
HBP_AGE35, //EN Age 35-39
HBP_AGE40, //EN Age 40-44
HBP_AGE45, //EN Age 45+
HBP_EDUC1, //EN Entered primary school but dropped out
HBP_EDUC2 //EN Graduated from primary school
};
partition BIRTH_AGE_PART { 35, 40, 45 }; //EN Age Groups
partition DUR_TIME_IN_PARITY { 1, 3, 6, 9, 12 }; //EN Duration episodes since last birth
////////////////////////////////////////////////////////////////////////////////////////////////////
// Parameters
////////////////////////////////////////////////////////////////////////////////////////////////////
parameters
{
//EN Fertility model selection
SELECTED_FERTILITY_MODEL selected_fertility_model;
//EN First Births
double FirstBirthRates[BIRTH1_GROUP][UNION_STATUS][FERTILE_AGE_RANGE][BIRTH1_LOC];
//EN Higher order births
double HigherOrderBirthsPara[HIGHER_BIRTHS_PARA][PARITY_RANGE2];
//EN Birth Trends
double BirthTrends[PARITY_RANGE1][SIM_YEAR_RANGE];
};
parameter_group PG03_Fertility_Top //EN Fertility
{
selected_fertility_model,
PG03a_Fertility_Model_A,
PG03b_Fertility_Model_B
};
parameter_group PG03b_Fertility_Model_B //EN Fertility: Refined Version
{
FirstBirthRates, HigherOrderBirthsPara,
BirthTrends
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Actor States and Functions
////////////////////////////////////////////////////////////////////////////////////////////////////
actor Person
{
//EN Population group for modeling of births
BIRTH1_GROUP birth1_group = (educ_one_fate == EOL_LOW ) ? B1G_00 :
(educ_one_fate == EOL_MEDIUM ) ? B1G_01 : B1G_02;
//EN Geographical location for modeling of first births
BIRTH1_LOC birth1_loc = aggregate( district_nat, BIRTH1_LOC);
FERTILE_AGE_RANGE fertile_age = COERCE(FERTILE_AGE_RANGE, integer_age); //EN Age
int birth_age_part = split(integer_age, BIRTH_AGE_PART); //EN Age group
UNION_STATUS union_status = (in_union) ? US_YES : US_NO; //EN Union Status
//EN Indicator re-set at birth events for calculation of time since last birth
logical in_this_parity = { TRUE };
//EN Indicator re-set at birt events calculating time since last birth in simulation time
logical in_this_parity_in_simulation = in_projected_time && in_this_parity;
//EN Time since last birth in the starting population or at creation (immigrants)
double time_since_last_birth_start = { 0.0 };
//EN Time since last birth in full years
int time_since_last_birth =
self_scheduling_int(active_spell_duration(in_this_parity_in_simulation, TRUE))
+ round(time_since_last_birth_start);
//EN Time index since the last birth event
int time_in_parity = split(time_since_last_birth, DUR_TIME_IN_PARITY);
void SetFertilityModelSwitches(); //EN Initializing model choices
hook SetFertilityModelSwitches, Start; //EN hooked to Start function
void GetImmigrantParityTimeBirth(); //EN Sample immigrants characteristics
hook GetImmigrantParityTimeBirth, ImmigrationEvent; //EN hook to ImmigrationEvent
TIME time_of_last_birth = { TIME_INFINITE }; //EN Time of last birth
void MakeBaby(); //EN Function creating a baby
event timeRefineModelBirthEvent, RefineModelBirthEvent; //EN Birth event
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////////////////////////
void Person::GetImmigrantParityTimeBirth()
{
if (sex == FEMALE )
{
if ( asWomenAgeEducDist[integer_age][educ_one_fate][district_immi]->Count() > 0 )
{
auto prHost = asWomenAgeEducDist[integer_age][educ_one_fate][district_immi]
->GetRandom(RandUniform(30));
parity = prHost-> parity;
if (parity > 0)
{
in_this_parity = FALSE;
time_since_last_birth_start = prHost->time_since_last_birth;
in_this_parity = TRUE;
}
}
else parity = 0; // no donor found
}
}
void Person::SetFertilityModelSwitches()
{
if (selected_fertility_model == SFM_MACRO) // Macro model (age only)
{
use_base_fertility_model = TRUE ; // Use the base model
use_base_fertility_for_alignment = FALSE ; // Base model not used for alignment
}
else if (selected_fertility_model == SFM_MICRO ) // Use the refined model un-aligned
{
use_base_fertility_model = FALSE ; // Do not use the base model
use_base_fertility_for_alignment = FALSE ; // Base model not used for alignment
}
else // Refined model with aligned births
{
use_base_fertility_model = FALSE ; // Do not use the base model
use_base_fertility_for_alignment = TRUE ; // Base model used for alignment
}
}
void Person::MakeBaby()
{
auto peChild = new Person; // Create and point to a new actor
peChild->Start(NULL, this); // Call Start() function of the new actor and pass own address
}
TIME Person::timeRefineModelBirthEvent()
{
double dEventTime = TIME_INFINITE;
double dHazard = 0.0;
// The base model to which output is aligned already produced a baby
if (baby_looking_for_mother)
{
dEventTime = WAIT(0);
}
// The refined model version is used
else if (is_potential_mother && !use_base_fertility_model)
{
// first birth
if (parity == 0)
{
dHazard = FirstBirthRates[birth1_group][union_status]
[RANGE_POS(FERTILE_AGE_RANGE, integer_age)][birth1_loc]
* BirthTrends[RANGE_POS(PARITY_RANGE1, parity+1)][RANGE_POS(SIM_YEAR_RANGE, calendar_year)];
}
// higher order births
else
{
// Baseline hazard and trend
dHazard = HigherOrderBirthsPara[time_in_parity][RANGE_POS(PARITY_RANGE2, parity+1)]
* BirthTrends[RANGE_POS(PARITY_RANGE1, parity+1)][RANGE_POS(SIM_YEAR_RANGE, calendar_year)];
// relative risk for older age
if (birth_age_part > 0) //not the baseline age group
{
dHazard = dHazard *
HigherOrderBirthsPara[HBP_AGE35 - 1 + birth_age_part][RANGE_POS(PARITY_RANGE2, parity+1)];
}
// relative risk for primary dropouts and graduates
if (educ_one_fate == EOL_MEDIUM)
{
dHazard = dHazard * HigherOrderBirthsPara[HBP_EDUC1][RANGE_POS(PARITY_RANGE2, parity+1)];
}
else if (educ_one_fate == EOL_HIGH)
{
dHazard = dHazard * HigherOrderBirthsPara[HBP_EDUC2][RANGE_POS(PARITY_RANGE2, parity+1)];
}
}
// schedule event
if (dHazard > 0.0) dEventTime = WAIT(-TIME(log(RandUniform(29)) / dHazard));
}
return dEventTime;
}
/* NOTE(Person.BirthEvent, EN)
At each birth event it has to be determined if the birth applies to the actor herself (unaligned model)
or if an appropriate mother for the birth still has to be found. The latter is used for aligned modes,
in which the birth events are produced by the macro model to which output is aligned, while the birth itself
is given by the potential mother with the shortest waiting time determined by the micro model.
*/
void Person::RefineModelBirthEvent()
{
// event applies to individual without alignment
if (!baby_looking_for_mother && !use_base_fertility_for_alignment)
{
// increase parity of this actor and create a baby linked to this actor
in_this_parity = FALSE; // initialize indicator
time_since_last_birth_start = 0.0; // time isnce last birth in starting population
parity++; // increment parity
auto peChild = new Person; // Create and point to a new actor
peChild->Start(NULL, this); // Call the Start() function of the new actor and pass own address
in_this_parity = TRUE; // reset indicator
time_of_last_birth = time; // record time
}
// aligned to macro-model: find women with shortest waing time to birth
else if (baby_looking_for_mother)
{
baby_looking_for_mother = FALSE;
auto peMother = asPotentialMothers->Item(0);
auto pePotentialMother = asPotentialMothers->Item(0);
double dShortestWaitTime = TIME_INFINITE;
// align number of births only
if ( selected_fertility_model == SFM_ALIGNE_BIRTHS )
{
double nNumber = asPotentialMothers->Count();
// find an appropriate mother for this birth event
for (double nJ = 0; nJ < nNumber; nJ++)
{
pePotentialMother = asPotentialMothers->Item(nJ);
double dCurrentWaitTime = pePotentialMother->timeRefineModelBirthEvent() - time;
if (dCurrentWaitTime < dShortestWaitTime)
{
peMother = pePotentialMother;
dShortestWaitTime = dCurrentWaitTime;
}
}
}
// align number of birth by age
else
{
double nNumber = asPotentialMothersByAge[RANGE_POS(FERTILE_AGE_RANGE, fertile_age)]->Count();
// find an appropriate mother for this birth event
for (double nJ = 0; nJ < nNumber; nJ++)
{
pePotentialMother = asPotentialMothersByAge[RANGE_POS(FERTILE_AGE_RANGE, fertile_age)]->Item(nJ);
double dCurrentWaitTime = pePotentialMother->timeRefineModelBirthEvent() - time;
if (dCurrentWaitTime < dShortestWaitTime)
{
peMother = pePotentialMother;
dShortestWaitTime = dCurrentWaitTime;
}
}
}
// increase parity of the selected mother and create a baby linked to her mother
peMother->in_this_parity = FALSE; // initialize indicator
peMother->time_since_last_birth_start = 0.0; // reset time before start of simulation
peMother->parity++; // increment parity
peMother->MakeBaby();
peMother->in_this_parity = TRUE; // reset indicator
peMother->time_of_last_birth = time;
}
}
table Person TabNumberBirthsByAge //EN Births by age
[in_projected_time]
{
{
parity //EN Births
}
* sim_year
* integer_age+
};
5.13.3. Initializations in the Start() Function¶
The states parity and time_since_last_birth_start are initiated in the Start() Function.
// Initialize states
if (person_type == PT_START) // Person comes from starting population
{
// (A) States from Starting population file
time = peObservation->pmc[PMC_BIRTH];
sex = (SEX)(int)peObservation->pmc[PMC_SEX];
family_role = peObservation->fam_role;
district_int = (DISTRICT_INT)(int)peObservation->pmc[PMC_DISTRICT];
district_birth = (DISTRICT_INT)(int)peObservation->pmc[PMC_DOB];
educ_one_startpop = (EDUC_ONE_LEVEL)(int)peObservation->pmc[PMC_EDUC];
ethnicity = (ETHNICITY)(int)peObservation->pmc[PMC_ETHNO];
if (peObservation->pmc[PMC_UNION] > time && peObservation->pmc[PMC_UNION] < MIN(SIM_YEAR_RANGE))
{
union1_startpop = peObservation->pmc[PMC_UNION];
}
if ( sex == FEMALE )
{
parity = (int)peObservation->pmc[PMC_PARITY];
if ( parity > 0 && MIN(SIM_YEAR_RANGE) > peObservation->pmc[PMC_LASTBIR] )
{
time_since_last_birth_start = MIN(SIM_YEAR_RANGE) - peObservation->pmc[PMC_LASTBIR];
}
}