Functions
Functions#
These are the packages that you will need to run the functions defined here. We will call this file using ams.func
sintax in the following routines.
import numpy as np
from numba import njit
numba is a compiler developed for python and can make your codes faster. See more about it here.
@njit
def wage(age = 10, edu = 5,
w_ag_j = 27.6, q = -0.983 , a1 = 0.066 , a2 = 0.0166, b_w_ag = 0.883):
"""
This function aims to estimate the wage for the village where the child lives
Args:
age (int, optional): Child age Defaults to 10.
edu (int, optional): Child education. Defaults to 5.
w_ag_j (float, optional): Adults wage in the village. Defaults to 27.6.
q (float, optional): Intercept. Defaults to -0.983.
a1 (float, optional): Age coefficient. Defaults to 0.066.
a2 (float, optional): Education coefficient. Defaults to 0.0166.
b_w_ag (float, optional): Adults wage coefficient. Defaults to 0.0166.
Returns:
w (float): This is the log wage base in the village where the child lives
"""
w = (q + a1*age + a2*edu + b_w_ag*np.log(w_ag_j))
return w
@njit
def grant(edu = 5, gender = 'girl'):
"""
This function aims to return the payment received by a child based on the payment schedule
defined for PROGRESSA (Table 1)
Args:
edu (int, optional): Child Education. Defaults to 5.
gender (str, optional): Child gender. The program has different payment schedules by
gender, can be 'girl' or 'boy'. Defaults to 'girl'.
Returns:
g (int): The amount payed for the child
"""
'''
Defining the payment structure for boys and girl in 1998 1st semester
'''
grant_b = {
3 : 130, 4 : 150, 5 : 190, 6 : 260,
7 : 380, 8 : 400, 9 : 420
}
grant_f = {
3 : 130, 4 : 150, 5 : 190, 6 : 260,
7 : 400, 8 : 440, 9 : 480
}
'''
Check the gender and define the structure to get the payment (g)
'''
if gender == 'girl':
grant_payment = grant_f
else:
grant_payment = grant_b
try:
g = grant_payment[edu]
except:
g = 0
return g
@njit
def budget_constraint(age, edu, school = 1, gender = 'girl', group = 'treatment',
g = 3.334, b_progressa = 0.0605, hr_day = 7.9, days = 5.1, weeks = 1.7, days_weeks = 14):
"""
This function returns the child budget constraint living in a village, conditional on working or
going to school
Args:
age (int): Child age.
edu (int): Child education.
school (int, optional): School or Work. Defaults to 1.
gender (str, optional): _description_. Defaults to 'girl'.
group (str, optional): In the treatment or control group. Defaults to 'treatment'.
g (float, optional): Coefficient in the utility for the grant. Defaults to 3.334.
b_progressa (float, optional): Progressa dummy coefficient. Defaults to 0.0605
hr_day (float, optional): Average hours worked per day. Defaults to 7.9.
days (float, optional): Average days worked. Defaults to 5.1.
weeks (int, optional): Average weeks worked. Defaults to 2.
days_weeks (int, optional): Days in the weeks worked. Defaults to 14.
Returns:
b (float): The budget constraint of the child in a particular village
"""
if group == 'treatment':
b = (1-school)*((np.exp(wage(age, edu) + b_progressa)*(hr_day*days*weeks))/(days_weeks)) + school*g*grant(edu,gender)/(days_weeks)
else:
b = ((1-school)*np.exp((wage(age, edu)))*(hr_day*days*weeks))/(days_weeks)
return b
@njit
def edu_cost(age = 10, edu = 5, mom_edu = 1, cost_sec = 9.5,
sec = 0, μ = -8.7060, b_age = 2.291, b_mom = -0.746, b_edu = -0.983, b_sec = 0.007):
"""
This function returns the child cost of going to school
Args:
age (int, optional): Child age. Defaults to 10.
mom_edu (int, optional): Mother's education. Defaults to 1.
edu (int, optional): Child education. Defaults to 5.
cost_sec (float, optional): Cost. Defaults to 9.5.
sec (int, optional): If the child is attending secondary school. Defaults to 1.
μ (float,optional): Shifter coefficient for the child. Defaults to -9.706.
b_age (float, optional): Age coefficient. Defaults to 2.291.
b_mom (float,optional): Mother background coefficient. Defaults to -0.746.
b_edu (float,optional): Education coefficient. Defaults to -0.983.
b_sec (float,optional): Secondary education coefficient. Defaults to 0.007.
Returns:
cost (float): The cost of going to school for the child
"""
if edu > 6:
sec = 1
cost = μ + b_mom*mom_edu + b_age*age + b_edu*edu + b_sec*cost_sec*sec
return cost
@njit
def utility(age = 10, edu = 5, school = 1, ϵ = 0, δ = 0.134, μ = -8.706):
"""
This function measures the utility gain for the child based on the choice of schooling or working
Args:
school (int, optional): School or Work. Defaults to 1.
edu (int, optional): Child education. Defaults to 5.
age (int, optional): Child age. Defaults to 10.
g (float, optional): Coefficient in the utility for the grant. Defaults to 3.334.
δ (float, optional): Coefficient in the utility for the grant. Defaults to 0.134.
μ (float, optional): Coefficient in the utility for the grant. Defaults to -8.706.
ϵ (float, optional): Shock for school taste
Returns:
u (float): Utility gain from the decision
"""
u = ((δ*budget_constraint(age, edu, school)) - edu_cost(age, edu, μ = μ) + ϵ)*school + (δ*budget_constraint(age, edu, school))*(1 - school)
return u
@njit
def terminal_v(α1 = 356.3809 , α2 = 0.2792, edu = 5):
"""
This function measures the utility gain of completing a determined x years of education bt age 18
Args:
edu (int, optional): Child education. Defaults to 5.
α1 (int, optional): Parameter 1. Defaults to 5.876
α2 (int, optional): Parameter 2. Defaults to -1.276
Returns:
tv (float) : Terminal value
"""
tv = α1/(1 + np.exp(-α2*edu))
return tv
@njit
def value_function(age = 10, edu = 5, school = 1, EV = np.nan, b = 0.9):
"""
Value function
Args:
age (int, optional): age of child. Defaults to 10.
edu (int, optional): education of child. Defaults to 5.
school (int, optional): school choice. Defaults to 1.
EV (float, optional): ev for next period. Defaults to np.nan.
b (float, optional): discount. Defaults to 0.9.
Returns:
float: valu function value
"""
v = utility(age, edu, school) + b*EV
return v
@njit
def logistic(eps = 0.0):
"""
Logistic function
Args:
eps (float, optional): epsilon value. Defaults to 0.0.
Returns:
float: value for cdf until eps
"""
l = 1/(1+np.exp(-(eps)))
return l
@njit
def trunc_change(eps = 0.0):
"""
This function transforms the variable to do integration
Args:
eps (float, optional): epsilon value. Defaults to 0.
Returns:
float: variable transformed
"""
x = (1+np.exp(-eps))**(-1)
return x
@njit
def trunc_school(ϵ_threshold = 0.0, u_threshold = 5.0):
"""
Truncated value from x threshold to +∞, where the child goes to school
Args:
ϵ_threshold (float, optional):
u_threshold (float, optional): _description_. Defaults to 5.
Returns:
float: truncated value calculated
"""
t = (u_threshold*np.log((1-u_threshold)/u_threshold) - np.log(1-u_threshold))\
*(np.exp(-ϵ_threshold)/(1+np.exp(-ϵ_threshold)));
return t
@njit
def prob_progress(edu=0):
"""
Progress of education function
Args:
edu (int, optional): education level. Defaults to 0.
Returns:
float: probability of progress
"""
if edu<=6:
p = 0.9
else:
p = 0.75
return p
@njit
def solution(age_max = 10):
# Probabilities of working
probs_w = np.empty((age_max,age_max))
probs_w[:] = np.nan
# Probabilities of school
probs_s = np.empty((age_max,age_max))
probs_s[:] = np.nan
# Threshold matrix
eps_t = np.empty((age_max,age_max))
eps_t[:] = np.nan
# Expected values matrix for L = 1
eve_w = np.empty((age_max,age_max))
eve_w[:] = np.nan
# Expected values matrix for L = 0
eve_s = np.empty((age_max,age_max))
eve_s[:] = np.nan
# Value function matrix
vf = np.empty((age_max,age_max))
vf[:] = np.nan
# SOLVING THE MODEL
for age in range(age_max,-1,-1):
for edu in range(0,age_max):
if edu >= age:
pass
else:
# For the threshold we will use the terminal value to capture the value of an specifiy education level in terms of utility
if age == age_max: #last period situation
tv_s = prob_progress(edu)*(terminal_v(edu = edu+1)) + (1 - prob_progress(edu))*(terminal_v(edu = edu))
tv_w = terminal_v(edu = edu)
eps_t[age-1,edu] = value_function(age,edu,1, EV = tv_s) - value_function(age,edu,0, EV = tv_w)
else: #Other periods will take the difference between value functions
tv_s = prob_progress(edu)*(vf[age, edu+1]) + (1 - prob_progress(edu))*(vf[age,edu])
tv_w = vf[age, edu]
eps_t[age-1,edu] = value_function(age,edu,1, EV = tv_s) - value_function(age,edu,0, EV = tv_w)
# Now we will calcute the probabilities of schooling and working based on the thresholds
probs_s[age-1,edu] = logistic(eps = eps_t[age-1,edu])
probs_w[age-1,edu] = 1 - probs_s[age-1,edu]
# Using the thresholds we will calculate the EVs
if age == age_max:
u_t = trunc_change(eps = eps_t[age-1,edu])
eve_s[age-1,edu] = value_function(age,edu,1, EV = tv_s) + trunc_school(ϵ_threshold = eps_t[age-1,edu], u_threshold = u_t)
eve_w[age-1,edu] = value_function(age,edu,0, EV = tv_w)
else:
u_t = trunc_change(eps = eps_t[age-1,edu])
eve_s[age-1,edu] = value_function(age,edu,1, EV = tv_s) + trunc_school(ϵ_threshold = eps_t[age-1,edu], u_threshold = u_t)
eve_w[age-1,edu] = value_function(age,edu,0, EV = tv_w)
vf[age-1,edu] = probs_s[age-1,edu]*eve_s[age-1,edu] + probs_w[age-1,edu]*eve_w[age-1,edu]
return probs_w, probs_s, eps_t, eve_w, eve_s, vf
def simulation(sim = 100, age_start = 6, age_max = 10, ϵ_mean = 0, ϵ_sd = 1, β = 0.9):
"""
This function simulates individuals using the AMS model solution as base
Args:
sim (int, optional): Number of individuals to be simulated. Defaults to 100.
age_start (int, optional): Starting age. Defaults to 6.
age_max (int, optional): Max number of years in adding to age_start (in this case 6 + 10 = 15)
15 years old is the last year simulated. Defaults to 10.
ϵ_mean (float, optional): Shock mean. Defaults to 0.
ϵ_sd (float, optional): Shock standart deviation. Defaults to 1.
β (float, optional): Discount factor. Defaults to 0.9
Returns:
DataFrame: Simulated dataframe
"""
probs_w, probs_s, eps_t, eve_w, eve_s, vf = solution()
# Creating matrices to store our results and create a dataframe with our individual
# School decision
S_dec = np.empty((age_max,sim))
S_dec[:] = np.nan
# Education level (current period)
ed_cur = np.empty((age_max,sim))
ed_cur[:] = np.nan
# Education level (next period)
ed_next = np.empty((age_max,sim))
ed_next[:] = np.nan
# Age
age_c = np.empty((age_max,sim))
age_c[:] = np.nan
# Shock
eps = np.empty((age_max,sim))
eps[:] = np.nan
# Utility
U = np.empty((age_max,sim))
U[:] = np.nan
# Comsumption
C = np.empty((age_max,sim))
C[:] = np.nan
# Now we will loop our simulated individuals using the probability matrices obtained using the model solution code.
for s in range(sim):
# All the individuals start with no education
edu = 0
for age in range(age_max):
# Actual child's age
age_c[age,s] = age_start + age
ed_cur[age,s] = edu
# Random shock to education cost
e_shock = np.random.logistic(loc = ϵ_mean, scale = ϵ_sd)
# Random shock to primary education sucess
e_sucess = np.random.uniform()
if age == age_max - 1:
s_ev = prob_progress(edu)*terminal_v(edu=edu+1) + (1 - prob_progress(edu))*terminal_v(edu=edu)
w_ev = terminal_v(edu=edu)
else:
s_ev = prob_progress(edu)*vf[age+1, edu+1] + (1 - prob_progress(edu))*vf[age+1,edu]
w_ev = vf[age+1, edu]
# Utility for choices
school = utility(age, edu, school=1, ϵ = e_shock) + β*s_ev
work = utility(age, edu, school=0, ϵ = e_shock) + β*w_ev
# Decision that max utility for individual
dec = max(school, work)
u = dec
if u == school:
if e_sucess <= prob_progress(edu):
suc = 1
else:
suc = 0
ed_next[age,s] = edu + suc
C[age,s] = budget_constraint(age, edu, school=1)
U[age,s] = dec
S_dec[age,s] = 1
else:
suc = 0
ed_next[age,s] = edu + suc
C[age,s] = budget_constraint(age, edu, school=0)
U[age,s] = dec
S_dec[age,s] = 0
eps[age,s] = e_shock
edu = edu + suc
# Now we can use the data created to build our dataframe
simulated_data = []
for i in range(sim):
s = {}
s['id'] = i
s['age'] = list(range(6,16))
s['edu'] = list(ed_cur[:,i])
s['edu_next'] = list(ed_next[:,i])
s['School'] = list(S_dec[:,i])
s['eps'] = list(eps[:,i])
s['U'] = list(U[:,i])
s['C'] = list(C[:,i])
s = pd.DataFrame(s)
simulated_data.append(s)
simulated_data = pd.concat(simulated_data, axis = 0).reset_index().drop('index', axis = 1)
return simulated_data