+"""
+-----------------------------------------------------------------
+Functions for created the matrix of ability levels, e. This can
+only be used for looking at the 25, 50, 70, 80, 90, 99, and 100th
+percentiles, as it uses fitted polynomials to those percentiles.
+-----------------------------------------------------------------
+"""
+
+importnumpyasnp
+importscipy.optimizeasopt
+importscipy.interpolateassi
+fromogcoreimportparameter_plotsaspp
+
+
+
+[docs]
+defarctan_func(xvals,a,b,c):
+r"""
+ This function generates predicted ability levels given data (xvals)
+ and parameters a, b, and c, from the following arctan function:
+
+ .. math::
+ y = (-a / \pi) * \arctan(b * x + c) + (a / 2)
+
+ Args:
+ xvals (Numpy array): data inputs to arctan function
+ a (scalar): scale parameter for arctan function
+ b (scalar): curvature parameter for arctan function
+ c (scalar): shift parameter for arctan function
+
+ Returns:
+ yvals (Numpy array): predicted values (output) of arctan
+ function
+
+ """
+ yvals=(-a/np.pi)*np.arctan(b*xvals+c)+(a/2)
+ returnyvals
+
+
+
+
+[docs]
+defarctan_deriv_func(xvals,a,b,c):
+r"""
+ This function generates predicted derivatives of arctan function
+ given data (xvals) and parameters a, b, and c. The functional form
+ of the derivative of the function is the following:
+
+ .. math::
+ y = - (a * b) / (\pi * (1 + (b * xvals + c)^2))
+
+ Args:
+ xvals (Numpy array): data inputs to arctan derivative function
+ a (scalar): scale parameter for arctan function
+ b (scalar): curvature parameter for arctan function
+ c (scalar): shift parameter for arctan function
+
+ Returns:
+ yvals (Numpy array): predicted values (output) of arctan
+ derivative function
+
+ """
+ yvals=-(a*b)/(np.pi*(1+(b*xvals+c)**2))
+ returnyvals
+
+
+
+
+[docs]
+defarc_error(abc_vals,params):
+"""
+ This function returns a vector of errors in the three criteria on
+ which the arctan function is fit to predict extrapolated ability in
+ ages 81 to 100.::
+
+ 1) The arctan function value at age 80 must match the estimated
+ original function value at age 80.
+ 2) The arctan function slope at age 80 must match the estimated
+ original function slope at age 80.
+ 3) The level of ability at age 100 must be a given fraction
+ (abil_deprec) below the ability level at age 80.
+
+ Args:
+ abc_vals (tuple): contains (a,b,c)
+
+ * a (scalar): scale parameter for arctan function
+ * b (scalar): curvature parameter for arctan function
+ * c (scalar): shift parameter for arctan function
+ params (tuple): contains (first_point, coef1, coef2, coef3,
+ abil_deprec)
+
+ * first_point (scalar): ability level at age 80, > 0
+ * coef1 (scalar): coefficient in log ability equation on
+ linear term in age
+ * coef2 (scalar): coefficient in log ability equation on
+ quadratic term in age
+ * coef3 (scalar): coefficient in log ability equation on
+ cubic term in age
+ * abil_deprec (scalar): ability depreciation rate between
+ ages 80 and 100, in (0, 1).
+
+ Returns:
+ error_vec (Numpy array): errors ([error1, error2, error3])
+
+ * error1 (scalar): error between ability level at age 80
+ from original function minus the predicted ability at
+ age 80 from the arctan function given a, b, and c
+ * error2 (scalar): error between the slope of the original
+ function at age 80 minus the slope of the arctan
+ function at age 80 given a, b, and c
+ * error3 (scalar): error between the ability level at age
+ 100 predicted by the original model value times
+ abil_deprec minus the ability predicted by the arctan
+ function at age 100 given a, b, and c
+
+ """
+ a,b,c=abc_vals
+ first_point,coef1,coef2,coef3,abil_deprec=params
+ error1=first_point-arctan_func(80,a,b,c)
+ if(3*coef3*80**2+2*coef2*80+coef1)<0:
+ error2=(
+ 3*coef3*80**2+2*coef2*80+coef1
+ )*first_point-arctan_deriv_func(80,a,b,c)
+ else:
+ error2=-0.02*first_point-arctan_deriv_func(80,a,b,c)
+ error3=abil_deprec*first_point-arctan_func(100,a,b,c)
+ error_vec=np.array([error1,error2,error3])
+
+ returnerror_vec
+
+
+
+
+[docs]
+defarctan_fit(first_point,coef1,coef2,coef3,abil_deprec,init_guesses):
+"""
+ This function fits an arctan function to the last 20 years of the
+ ability levels of a particular ability group to extrapolate
+ abilities by trying to match the slope in the 80th year and the
+ ability depreciation rate between years 80 and 100.
+
+ Args:
+ first_point (scalar): ability level at age 80, > 0
+ coef1 (scalar): coefficient in log ability equation on linear
+ term in age
+ coef2 (scalar): coefficient in log ability equation on
+ quadratic term in age
+ coef3 (scalar): coefficient in log ability equation on cubic
+ term in age
+ abil_deprec (scalar): ability depreciation rate between
+ ages 80 and 100, in (0, 1)
+ init_guesses (Numpy array): initial guesses
+
+ Returns:
+ abil_last (Numpy array): extrapolated ability levels for ages
+ 81 to 100, length 20
+
+ """
+ params=[first_point,coef1,coef2,coef3,abil_deprec]
+ solution=opt.root(arc_error,init_guesses,args=params,method="lm")
+ [a,b,c]=solution.x
+ old_ages=np.linspace(81,100,20)
+ abil_last=arctan_func(old_ages,a,b,c)
+ returnabil_last
+
+
+
+
+[docs]
+defget_e_interp(S,age_wgts,age_wgts_80,abil_wgts,plot_path=None):
+"""
+ This function takes a source matrix of lifetime earnings profiles
+ (abilities, emat) of size (80, 7), where 80 is the number of ages
+ and 7 is the number of ability types in the source matrix, and
+ interpolates new values of a new S x J sized matrix of abilities
+ using linear interpolation. [NOTE: For this application, cubic
+ spline interpolation introduces too much curvature.]
+
+ This function also includes the two cases in which J = 9 and J = 10
+ that include higher lifetime earning percentiles calibrated using
+ Piketty and Saez (2003).
+
+
+ Args:
+ S (int): number of ages to interpolate. This method assumes that
+ ages are evenly spaced between the beginning of the 21st
+ year and the end of the 100th year, >= 3
+ age_wgts (Numpy array): distribution of population in each age
+ for the interpolated ages, length S
+ age_wgts_80 (Numpy array): percent of population in each
+ one-year age from 21 to 100, length 80
+ abil_wgts (Numpy array): distribution of population in each
+ ability group, length J
+ plot_path (str)): if True, creates plots of emat_orig and the new
+ interpolated emat_new
+
+ Returns:
+ emat_new_scaled (Numpy array): interpolated ability matrix scaled
+ so that population-weighted average is 1, size SxJ
+
+ """
+ # Get original 80 x 7 ability matrix
+ abil_wgts_orig=np.array([0.25,0.25,0.2,0.1,0.1,0.09,0.01])
+ emat_orig=get_e_orig(age_wgts_80,abil_wgts_orig,plot_path)
+ if(
+ S==80
+ andnp.array_equal(
+ np.squeeze(abil_wgts),
+ np.array([0.25,0.25,0.2,0.1,0.1,0.09,0.01]),
+ )
+ isTrue
+ ):
+ emat_new_scaled=emat_orig
+ elif(
+ S==80
+ andnp.array_equal(
+ np.squeeze(abil_wgts),
+ np.array(
+ [0.25,0.25,0.2,0.1,0.1,0.09,0.005,0.004,0.0009,0.0001]
+ ),
+ )
+ isTrue
+ ):
+ emat_new=np.zeros((S,len(abil_wgts)))
+ emat_new[:,:7]=emat_orig
+ # Create profiles for top 0.5%, top 0.1% and top 0.01% using
+ # Piketty and Saez estimates
+ # (https://eml.berkeley.edu/~saez/pikettyqje.pdf)
+ # updated for 2018 to create scaling factor
+ # assumption is that profile shape of these top 3 groups are
+ # same as the top 1% estimated in tax data, just scaled up by
+ # ratio determined from P&S 2018 estimates (Table 0, ex cap gains)
+ emat_new[:,5]=emat_orig[:,-2]*1.25
+ emat_new[:,6]=emat_orig[:,-1]*0.458759521*2.75
+ emat_new[:,7]=emat_orig[:,-1]*0.847252448*3.5
+ emat_new[:,8]=emat_orig[:,-1]*2.713698465*3.5
+ emat_new[:,9]=emat_orig[:,-1]*18.74863983*4.0
+ emat_new_scaled=(
+ emat_new
+ /(
+ emat_new*age_wgts.reshape(80,1)*abil_wgts.reshape(1,10)
+ ).sum()
+ )
+ elif(
+ S==80
+ andnp.array_equal(
+ np.squeeze(abil_wgts),
+ np.array([0.25,0.25,0.2,0.1,0.1,0.09,0.005,0.004,0.001]),
+ )
+ isTrue
+ ):
+ emat_new=np.zeros((S,len(abil_wgts)))
+ emat_new[:,:7]=emat_orig
+ # Create profiles for top 0.5%, top 0.1% using
+ # Piketty and Saez estimates
+ # (https://eml.berkeley.edu/~saez/pikettyqje.pdf)
+ # updated for 2018 to create scaling factor
+ # assumption is that profile shape of these top 3 groups are
+ # same as the top 1% estimated in tax data, just scaled up by
+ # ratio determined from P&S 2018 estimates (Table 0, ex cap gains)
+ emat_new[:,6]=emat_orig[:,-1]*0.458759521
+ emat_new[:,7]=emat_orig[:,-1]*0.847252448
+ emat_new[:,8]=emat_orig[:,-1]*4.317192601
+ emat_new_scaled=(
+ emat_new
+ /(
+ emat_new*age_wgts.reshape(80,1)*abil_wgts.reshape(1,9)
+ ).sum()
+ )
+ else:
+ # generate abil_midp vector
+ J=abil_wgts.shape[0]
+ abil_midp=np.zeros(J)
+ pct_lb=0.0
+ forjinrange(J):
+ abil_midp[j]=pct_lb+0.5*abil_wgts[j]
+ pct_lb+=abil_wgts[j]
+
+ # Make sure that values in abil_midp are within interpolating
+ # bounds set by the hard coded abil_wgts_orig
+ ifabil_midp.min()<0.125orabil_midp.max()>0.995:
+ err=(
+ "One or more entries in abils vector is outside the "
+ +"allowable bounds."
+ )
+ raiseRuntimeError(err)
+
+ emat_j_midp=np.array(
+ [0.125,0.375,0.600,0.750,0.850,0.945,0.995]
+ )
+ emat_s_midp=np.linspace(20.5,99.5,80)
+ emat_j_mesh,emat_s_mesh=np.meshgrid(emat_j_midp,emat_s_midp)
+ newstep=80/S
+ new_s_midp=np.linspace(20+0.5*newstep,100-0.5*newstep,S)
+ new_j_mesh,new_s_mesh=np.meshgrid(abil_midp,new_s_midp)
+ newcoords=np.hstack(
+ (
+ emat_s_mesh.reshape((80*7,1)),
+ emat_j_mesh.reshape((80*7,1)),
+ )
+ )
+ emat_new=si.griddata(
+ newcoords,
+ emat_orig.flatten(),
+ (new_s_mesh,new_j_mesh),
+ method="linear",
+ )
+ emat_new_scaled=(
+ emat_new
+ /(
+ emat_new*age_wgts.reshape(S,1)*abil_wgts.reshape(1,J)
+ ).sum()
+ )
+
+ ifplot_pathisnotNone:
+ kwargs={"path":plot_path,"filesuffix":"_intrp_scaled"}
+ pp.plot_income_data(
+ new_s_midp,
+ abil_midp,
+ abil_wgts,
+ emat_new_scaled,
+ plot_path,
+ **kwargs,
+ )
+
+ returnemat_new_scaled
+
+
+
+
+[docs]
+defget_e_orig(age_wgts,abil_wgts,plot_path=None):
+"""
+ This function generates the 80 x 7 matrix of lifetime earnings
+ ability profiles, corresponding to annual ages from 21 to 100 and to
+ paths based on income percentiles 0-25, 25-50, 50-70, 70-80, 80-90,
+ 90-99, 99-100. The ergodic population distribution is an input in
+ order to rescale the paths so that the weighted average equals 1.
+
+ The base curves are the ones in OG-USA, which are then adjusted for ZAF.
+
+ The polynomials are of the form
+
+ .. math::
+ \ln(abil) = \alpha + \beta_{1}\text{age} + \beta_{2}\text{age}^2
+ + \beta_{3}\text{age}^3
+
+ To calibrate for ZAF, the USA curves are adjusted in 2 ways (in this order)
+ 1) Adjustment by income (J): adjust the gaps between the J-income earning curves
+ using data from WID.
+ 2) Adjustment by age (S): adjust the shape/distribution of each J-income earning
+ profile curve using data from NTA.
+
+ The methodology is described here:
+ https://github.com/EAPD-DRB/OG-ZAF/issues/18#issuecomment-1368580323
+
+ Args:
+ age_wgts (Numpy array): ergodic age distribution, length S
+ abil_wgts (Numpy array): population weights in each lifetime
+ earnings group, length J
+ plot_path (str): Path to save plots to
+
+ Returns:
+ e_orig_scaled (Numpy array): = lifetime ability profiles scaled
+ so that population-weighted average is 1, size SxJ
+
+ """
+ # Return and error if age_wgts is not a vector of size (80,)
+ ifage_wgts.shape[0]!=80:
+ err="Vector age_wgts does not have 80 elements."
+ raiseRuntimeError(err)
+ # Return and error if abil_wgts is not a vector of size (7,)
+ ifabil_wgts.shape[0]!=7:
+ err="Vector abil_wgts does not have 7 elements."
+ raiseRuntimeError(err)
+
+ # 1) Generate polynomials using USA data and use them to get income profiles for
+ # ages 21 to 80.
+ one=np.array(
+ [
+ -0.09720122,
+ 0.05995294,
+ 0.17654618,
+ 0.21168263,
+ 0.21638731,
+ 0.04500235,
+ 0.09229392,
+ ]
+ )
+ two=np.array(
+ [
+ 0.00247639,
+ -0.00004086,
+ -0.00240656,
+ -0.00306555,
+ -0.00321041,
+ 0.00094253,
+ 0.00012902,
+ ]
+ )
+ three=np.array(
+ [
+ -0.00001842,
+ -0.00000521,
+ 0.00001039,
+ 0.00001438,
+ 0.00001579,
+ -0.00001470,
+ -0.00001169,
+ ]
+ )
+ const=np.array(
+ [
+ 3.41e00,
+ 0.69689692,
+ -0.78761958,
+ -1.11e00,
+ -0.93939272,
+ 1.60e00,
+ 1.89e00,
+ ]
+ )
+ ages_short=np.tile(np.linspace(21,80,60).reshape((60,1)),(1,7))
+ log_abil_paths=(
+ const
+ +(one*ages_short)
+ +(two*(ages_short**2))
+ +(three*(ages_short**3))
+ )
+
+ # New estimated coefficients for ZAF after adjustment by income (J) and by age (S)
+ const=np.array(
+ [
+ 1.10766851280735,
+ -1.47205271208099,
+ -2.79826519632522,
+ -2.84592025503416,
+ -2.33264177437992,
+ 0.820108734133472,
+ 0.573684959034946,
+ ]
+ )
+ one=np.array(
+ [
+ -0.0577752937758472,
+ 0.0993788662241527,
+ 0.215972106224152,
+ 0.251108556224153,
+ 0.255813236224153,
+ 0.0844282762241525,
+ 0.131719846224152,
+ ]
+ )
+ two=np.array(
+ [
+ 0.00313926193376278,
+ 0.000622011933762788,
+ -0.00174368806623721,
+ -0.00240267806623721,
+ -0.00254753806623722,
+ 0.00160540193376279,
+ 0.000791891933762785,
+ ]
+ )
+ three=np.array(
+ [
+ -0.000035350068460927,
+ -2.21400684609271e-05,
+ -6.54006846092713e-06,
+ -2.55006846092713e-06,
+ -1.14006846092704e-06,
+ -3.16300684609271e-05,
+ -2.86200684609271e-05,
+ ]
+ )
+ # compute the lifetime income profiles using the new coefficients
+ ages_short_adj=np.tile(np.linspace(21,80,60).reshape((60,1)),(1,7))
+ log_abil_paths_adj=(
+ const
+ +(one*ages_short_adj)
+ +(two*(ages_short_adj**2))
+ +(three*(ages_short_adj**3))
+ )
+ abil_paths_adj=np.exp(log_abil_paths_adj)
+
+ e_orig=np.zeros((80,7))
+ e_orig[:60,:]=abil_paths_adj
+ e_orig[60:,:]=0.0
+
+ # 2) Forecast (with some art) the path of the final 20 years of
+ # ability types. This following variable is what percentage of
+ # ability at age 80 ability falls to at age 100. In general, we
+ # wanted people to lose half of their ability over a 20-year
+ # period. The first entry is 0.47, though, because nothing higher
+ # would converge. The second-to-last is 0.7 because this group
+ # actually has a slightly higher ability at age 80 than the last
+ # group, so this value makes it decrease more so it ends up being
+ # monotonic.
+ abil_deprec=np.array([0.47,0.5,0.5,0.5,0.5,0.7,0.5])
+ # Initial guesses for the arctan. They're pretty sensitive.
+ init_guesses=np.array(
+ [
+ [58,0.0756438545595,-5.6940142786],
+ [27,0.069,-5],
+ [35,0.06,-5],
+ [37,0.339936555352,-33.5987329144],
+ [70.5229181668,0.0701993896947,-6.37746859905],
+ [35,0.06,-5],
+ [35,0.06,-5],
+ ]
+ )
+ forjinrange(7):
+ e_orig[60:,j]=arctan_fit(
+ e_orig[59,j],
+ one[j],
+ two[j],
+ three[j],
+ abil_deprec[j],
+ init_guesses[j],
+ )
+
+ # 3) Rescale the lifetime earnings path matrix so that the
+ # population weighted average equals 1.
+ e_orig_scaled=(
+ e_orig
+ /(e_orig*age_wgts.reshape(80,1)*abil_wgts.reshape(1,7)).sum()
+ )
+
+ ifplot_pathisnotNone:
+ ages_long=np.linspace(21,100,80)
+ abil_midp=np.array([12.5,37.5,60.0,75.0,85.0,94.5,99.5])
+ # Plot original unscaled 80 x 7 ability matrix
+ kwargs={"path":plot_path,"filesuffix":"_orig_unscaled"}
+ pp.plot_income_data(ages_long,abil_midp,abil_wgts,e_orig,**kwargs)
+
+ # Plot original scaled 80 x 7 ability matrix
+ kwargs={"path":plot_path,"filesuffix":"_orig_scaled"}
+ pp.plot_income_data(
+ ages_long,
+ abil_midp,
+ abil_wgts,
+ e_orig_scaled,
+ **kwargs,
+ )
+
+ returne_orig_scaled
+importpandasaspd
+importnumpyasnp
+fromogzaf.constantsimportCONS_DICT,PROD_DICT
+
+"""
+Read in Social Accounting Matrix (SAM) file
+"""
+# Read in SAM file
+storage_options={"User-Agent":"Mozilla/5.0"}
+SAM_path="https://www.wider.unu.edu/sites/default/files/Data/SASAM-2015-Data-Resource.xlsx"
+SAM=pd.read_excel(
+ SAM_path,
+ sheet_name="Micro SAM 2015",
+ skiprows=6,
+ index_col=0,
+ storage_options=storage_options,
+)
+
+
+
+[docs]
+defget_alpha_c(sam=SAM,cons_dict=CONS_DICT):
+"""
+ Calibrate the alpha_c vector, showing the shares of household
+ expenditures for each consumption category
+
+ Args:
+ sam (pd.DataFrame): SAM file
+ cons_dict (dict): Dictionary of consumption categories
+
+ Returns:
+ alpha_c (dict): Dictionary of shares of household expenditures
+ """
+ alpha_c={}
+ overall_sum=0
+ forkey,valueincons_dict.items():
+ # note the subtraction of the row to focus on domestic consumption
+ category_total=(
+ sam.loc[sam.index.isin(value),"total"].sum()
+ -sam.loc[sam.index.isin(value),"row"].sum()
+ )
+ alpha_c[key]=category_total
+ overall_sum+=category_total
+ forkey,valueincons_dict.items():
+ alpha_c[key]=alpha_c[key]/overall_sum
+
+ returnalpha_c
+
+
+
+
+[docs]
+defget_io_matrix(sam=SAM,cons_dict=CONS_DICT,prod_dict=PROD_DICT):
+"""
+ Calibrate the io_matrix array. This array relates the share of each
+ production category in each consumption category
+
+ Args:
+ sam (pd.DataFrame): SAM file
+ cons_dict (dict): Dictionary of consumption categories
+ prod_dict (dict): Dictionary of production categories
+
+ Returns:
+ io_df (pd.DataFrame): Dataframe of io_matrix
+ """
+ # Create initial matrix as dataframe of 0's to fill in
+ io_dict={}
+ forkeyinprod_dict.keys():
+ io_dict[key]=np.zeros(len(cons_dict.keys()))
+ io_df=pd.DataFrame(io_dict,index=cons_dict.keys())
+ # Fill in the matrix
+ # Note, each cell in the SAM represents a payment from the columns
+ # account to the row account
+ # (see https://www.un.org/en/development/desa/policy/capacity/presentations/manila/6_sam_mams_philippines.pdf)
+ # We are thus going to take the consumption categories from rows and
+ # the production categories from columns
+ forck,cvincons_dict.items():
+ forpk,pvinprod_dict.items():
+ io_df.loc[io_df.index==ck,pk]=sam.loc[
+ sam.index.isin(cv),pv
+ ].values.sum()
+ # change from levels to share (where each row sums to one)
+ io_df=io_df.div(io_df.sum(axis=1),axis=0)
+
+ returnio_df
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/ogzaf/macro_params.html b/_modules/ogzaf/macro_params.html
index 8dc76d4..0593309 100644
--- a/_modules/ogzaf/macro_params.html
+++ b/_modules/ogzaf/macro_params.html
@@ -186,21 +186,24 @@
end=datetime.date.today()# go through todaybaseline_date=datetime.datetime(2019,3,31)baseline_yearquarter=(
- "2019Q1"# The WB QPSD database has the date in YYYYQQ format
+ "2024Q1"# The WB QPSD database has the date in YYYYQQ format)"""
@@ -408,43 +410,43 @@
Source code for ogzaf.macro_params
}# pull series of interest using pandas_datareader
- wb_data_a=wb.download(
- indicator=wb_a_variable_dict.values(),
- country=country_iso,
- start=start,
- end=end,
- )
- wb_data_a.rename(
- columns=dict((y,x)forx,yinwb_a_variable_dict.items()),
- inplace=True,
- )
+ # wb_data_a = wb.download(
+ # indicator=wb_a_variable_dict.values(),
+ # country=country_iso,
+ # start=start,
+ # end=end,
+ # )
+ # wb_data_a.rename(
+ # columns=dict((y, x) for x, y in wb_a_variable_dict.items()),
+ # inplace=True,
+ # )""" This retrieves quarterly data from the World Bank Quarterly Public Sector Debt database. The command extracts all available data even when start and end dates are specified. """
- wb_q_variable_dict={
- "Gross PSD USD - domestic creditors":"DP.DOD.DECD.CR.PS.CD",
- "Gross PSD USD - external creditors":"DP.DOD.DECX.CR.PS.CD",
- "Gross PSD Gen Gov - percentage of GDP":"DP.DOD.DECT.CR.GG.Z1",
- }
+ # wb_q_variable_dict = {
+ # "Gross PSD USD - domestic creditors": "DP.DOD.DECD.CR.PS.CD",
+ # "Gross PSD USD - external creditors": "DP.DOD.DECX.CR.PS.CD",
+ # "Gross PSD Gen Gov - percentage of GDP": "DP.DOD.DECT.CR.GG.Z1",
+ # }# pull series of interest using pandas_datareader
- wb_data_q=wb.download(
- indicator=wb_q_variable_dict.values(),
- country=country_iso,
- start=start,
- end=end,
- )
- wb_data_q.rename(
- columns=dict((y,x)forx,yinwb_q_variable_dict.items()),
- inplace=True,
- )
-
- # Remove the hierarchical index (country and year) of wb_data_q and create a single row index using year
- wb_data_q=wb_data_q.reset_index()
- wb_data_q=wb_data_q.set_index("year")
+ # wb_data_q = wb.download(
+ # indicator=wb_q_variable_dict.values(),
+ # country=country_iso,
+ # start=start,
+ # end=end,
+ # )
+ # wb_data_q.rename(
+ # columns=dict((y, x) for x, y in wb_q_variable_dict.items()),
+ # inplace=True,
+ # )
+
+ # # Remove the hierarchical index (country and year) of wb_data_q and create a single row index using year
+ # wb_data_q = wb_data_q.reset_index()
+ # wb_data_q = wb_data_q.set_index("year")""" This retrieves labour share data from the ILOSTAT Data API
@@ -474,92 +476,45 @@
)/100)
- # 1
- # - pd.Series(
- # (fred_data["Labor share of income"]).loc[baseline_yearquarter]
- # ).mean()]# find g_y
- macro_parameters["g_y_annual"]=(
- wb_data_a["GDP per capita (constant 2015 US$)"].pct_change(-1).mean()
- )
+ # macro_parameters["g_y_annual"] = (
+ # wb_data_a["GDP per capita (constant 2015 US$)"].pct_change(-1).mean()
+ # )""""
- We want to use the non linear relationship estimated by Li, Magud, Werner, Witte (2021), available here: https://www.imf.org/en/Publications/WP/Issues/2021/06/04/The-Long-Run-Impact-of-Sovereign-Yields-on-Corporate-Yields-in-Emerging-Markets-50224
+ We want to use the non linear relationship estimated by
+ Li, Magud, Werner, Witte (2021), available here:
+ https://www.imf.org/en/Publications/WP/Issues/2021/06/04/The-Long-Run-Impact-of-Sovereign-Yields-on-Corporate-Yields-in-Emerging-Markets-50224 Steps:
- 1) Generate modelled corporate yields (corp_yhat) for a range of sovereign yields (sov_y) using the estimated equation in col 2 of table 8 (and figure 3).
- 2) Estimate the OLS using sovereign yields as the dependent variable
+ 1) Generate modelled corporate yields (corp_yhat) for a range of
+ sovereign yields (sov_y) using the estimated equation in col 2 of
+ table 8 (and figure 3). 2) Estimate the OLS using sovereign yields
+ as the dependent variable """# # estimate r_gov_shift and r_gov_scale
@@ -598,17 +553,15 @@
Source code for ogzaf.macro_params
corp_yhat,)res=mod.fit()
- # first term is the constant and needs to be divided by 100 to have the correct unit. Second term is the coefficient
+ # first term is the constant and needs to be divided by 100 to have
+ # the correct unit. Second term is the coefficientmacro_parameters["r_gov_shift"]=[(-res.params[0]/100)
- ]# constant = 0.0337662504
+ ]# constant = -0.0337662504macro_parameters["r_gov_scale"]=[res.params[1]]# coefficient = 0.24484764
- # macro_parameters["r_gov_shift"] = [-0.0337662504]
- # macro_parameters["r_gov_scale"] = [0.24484764]
-
returnmacro_parameters
+[docs]
+classCustomHttpAdapter(requests.adapters.HTTPAdapter):
+"""
+ The UN Data Portal server doesn't support "RFC 5746 secure renegotiation". This causes and error when the client is using OpenSSL 3, which enforces that standard by default.
+ The fix is to create a custom SSL context that allows for legacy connections. This defines a function get_legacy_session() that should be used instead of requests().
+ """
+
+ # "Transport adapter" that allows us to use custom ssl_context.
+ def__init__(self,ssl_context=None,**kwargs):
+ self.ssl_context=ssl_context
+ super().__init__(**kwargs)
+
+
+
+
+
+defget_legacy_session():
+ ctx=ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+ ctx.options|=0x4# OP_LEGACY_SERVER_CONNECT #in Python 3.12 you will be able to switch from 0x4 to ssl.OP_LEGACY_SERVER_CONNECT.
+ session=requests.session()
+ session.mount("https://",CustomHttpAdapter(ctx))
+ returnsession
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_sources/content/api/calibrate.rst b/_sources/content/api/calibrate.rst
index d06daf0..b986dd0 100644
--- a/_sources/content/api/calibrate.rst
+++ b/_sources/content/api/calibrate.rst
@@ -11,4 +11,4 @@ ogzaf.calibrate
.. currentmodule:: ogzaf.calibrate
.. autoclass:: Calibration
- :members: get_tax_function_parameters, read_tax_func_estimate, get_dict
+ :members: get_dict
diff --git a/_sources/content/api/demographics.rst b/_sources/content/api/demographics.rst
deleted file mode 100644
index d851109..0000000
--- a/_sources/content/api/demographics.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-.. _demographics:
-
-Demographics Functions
-========================
-
-**demographics.py modules**
-
-ogzaf.demographics
-------------------------------------------
-
-.. automodule:: ogzaf.demographics
- :members: get_un_fert_data, get_un_mort_data, get_wb_infmort_rate,
- get_un_pop_data, get_fert, get_mort, pop_rebin, get_imm_resid,
- immsolve, get_pop_objs, extrap_exp_3, extrap_arctan_3,
- ab_zero_eqs_exp_func, b_zero_eq_arctan_func
diff --git a/_sources/content/api/income.rst b/_sources/content/api/income.rst
new file mode 100644
index 0000000..9778c01
--- /dev/null
+++ b/_sources/content/api/income.rst
@@ -0,0 +1,13 @@
+.. _income:
+
+Life-cycle productivity profiles
+====================================
+
+**income.py modules**
+
+ogzaf.income
+------------------------------------------
+
+.. automodule:: ogzaf.income
+ :members: arctan_func, arctan_deriv_func, arc_error,
+ arctan_fit, get_e_interp, get_e_orig
diff --git a/_sources/content/api/input_output.rst b/_sources/content/api/input_output.rst
new file mode 100644
index 0000000..a249760
--- /dev/null
+++ b/_sources/content/api/input_output.rst
@@ -0,0 +1,12 @@
+.. _input_output:
+
+Output-Consumption Bridge
+====================================
+
+**input_output.py modules**
+
+ogzaf.input_output
+------------------------------------------
+
+.. automodule:: ogzaf.input_output
+ :members: get_alpha_c, get_io_matrix
diff --git a/_sources/content/api/public_api.rst b/_sources/content/api/public_api.rst
index 546de01..09ce241 100644
--- a/_sources/content/api/public_api.rst
+++ b/_sources/content/api/public_api.rst
@@ -12,5 +12,7 @@ There is also a link to the source code for each documented member.
:maxdepth: 1
calibrate
- demographics
+ income
+ input_output
macro_params
+ utils
diff --git a/_sources/content/api/utils.rst b/_sources/content/api/utils.rst
new file mode 100644
index 0000000..cf9a683
--- /dev/null
+++ b/_sources/content/api/utils.rst
@@ -0,0 +1,17 @@
+.. _utils:
+
+Calibration Utilities
+====================================
+
+**utils.py modules**
+
+ogzaf.utils
+------------------------------------------
+
+.. currentmodule:: ogzaf.utils
+
+.. autoclass:: CustomHttpAdapter
+ :members: init_poolmanager
+
+.. automodule:: ogzaf.utils
+ :members: get_legacy_session
diff --git a/_sources/content/calibration/bequests.md b/_sources/content/calibration/bequests.md
deleted file mode 100644
index 3ed68f1..0000000
--- a/_sources/content/calibration/bequests.md
+++ /dev/null
@@ -1,8 +0,0 @@
-(Chap_Beq)=
-# Bequest Process Calibration
-
-[TODO: This chapter needs to be finished.]
-
-This chapter describes how we calibrate the distribution of total bequests $BQ_t$ to each living household of age $s$ and lifetime income group $j$. The matrix that governs this distribution $\zeta_{j,s}$ is seen in the household budget constraint {eq}`EqHHBC`.
-
-A large number of papers study the effects of different bequest motives and specifications on the distribution of wealth, though there is no consensus regarding the true bequest transmission process. See {cite}`DeNardiYang:2014`, {cite}`DeNardi:2004`, {cite}`Nishiyama:2002`, {cite}`Laitner:2001`, {cite}`GokhaleEtAl:2000`, {cite}`GaleScholz:1994`, {cite}`Hurd:1989`, {cite}`VentiWise:1988`, {cite}`KotlikoffSummers:1981`, and {cite}`Wolff:2015`.
diff --git a/_sources/content/calibration/demographics.md b/_sources/content/calibration/demographics.md
index d4d96de..ce09f62 100644
--- a/_sources/content/calibration/demographics.md
+++ b/_sources/content/calibration/demographics.md
@@ -13,7 +13,7 @@ kernelspec:
(Chap_Demog)=
# Demographics
-We start the `OG-ZAF` section on modeling the household with a description of the demographics of the model. {cite}`Nishiyama:2015` and {cite}`DeBackerEtAl:2019` have recently shown that demographic dynamics are likely the biggest influence on macroeconomic time series, exhibiting more influence than fiscal variables or household preference parameters.
+Demographics are a key component of the macroeconmic model. {cite}`Nishiyama:2015` and {cite}`DeBackerEtAl:2019` have recently shown that demographic dynamics are likely the biggest influence on macroeconomic time series, exhibiting more influence than fiscal variables or household preference parameters.
In this chapter, we characterize the equations and parameters that govern the transition dynamics of the population distribution by age. In `OG-ZAF`, we take the approach of taking mortality rates and fertility rates from outside estimates. But we estimate our immigration rates as residuals using the mortality rates, fertility rates, and at least two consecutive periods of population distribution data. This approach makes sense if one is modeling a country in which one is not confident in the immigration rate data. If the country has good immigration data, then the immigration residual approach we describe below can be skipped.
@@ -22,11 +22,11 @@ We define $\omega_{s,t}$ as the number of households of age $s$ alive at time $t
The population of agents of each age in each period $\omega_{s,t}$ evolves according to the following function,
```{math}
:label: EqPopLawofmotion
- \omega_{1,t+1} &= (1 - \rho_0)\sum_{s=1}^{E+S} f_s\omega_{s,t} + i_1\omega_{1,t}\quad\forall t \\
- \omega_{s+1,t+1} &= (1 - \rho_s)\omega_{s,t} + i_{s+1}\omega_{s+1,t}\quad\forall t\quad\text{and}\quad 1\leq s \leq E+S-1
+ \omega_{1,t+1} &= (1 - \rho_{0,t})\sum_{s=1}^{E+S} f_{s,t}\omega_{s,t} + i_1\omega_{1,t}\quad\forall t \\
+ \omega_{s+1,t+1} &= (1 - \rho_{s,t})\omega_{s,t} + i_{s+1,t}\omega_{s+1,t}\quad\forall t\quad\text{and}\quad 1\leq s \leq E+S-1
```
-where $f_s\geq 0$ is an age-specific fertility rate, $i_s$ is an age-specific net immigration rate, $\rho_s$ is an age-specific mortality hazard rate, and $\rho_0$ is an infant mortality rate.[^houseprob_note] The total population in the economy $N_t$ at any period is simply the sum of households in the economy, the population growth rate in any period $t$ from the previous period $t-1$ is $g_{n,t}$, $\tilde{N}_t$ is the working age population, and $\tilde{g}_{n,t}$ is the working age population growth rate in any period $t$ from the previous period $t-1$.
+where $f_{s,t}\geq 0$ is an age-specific fertility rate, $i_{s,t}$ is an age-specific net immigration rate, $\rho_{s,t}$ is an age-specific mortality hazard rate, and $\rho_{0,t}$ is an infant mortality rate.[^houseprob_note] The total population in the economy $N_t$ at any period is simply the sum of households in the economy, the population growth rate in any period $t$ from the previous period $t-1$ is $g_{n,t}$, $\tilde{N}_t$ is the working age population, and $\tilde{g}_{n,t}$ is the working age population growth rate in any period $t$ from the previous period $t-1$.
```{math}
:label: EqPopN
@@ -48,51 +48,37 @@ where $f_s\geq 0$ is an age-specific fertility rate, $i_s$ is an age-specific ne
\tilde{g}_{n,t+1} \equiv \frac{\tilde{N}_{t+1}}{\tilde{N}_t} - 1 \quad\forall t
```
-We discuss the approach to estimating fertility rates $f_s$, mortality rates $\rho_s$, and immigration rates $i_s$ in Sections {ref}`SecDemogFert`, {ref}`SecDemogMort`, and {ref}`SecDemogImm`.
+We discuss the approach to estimating fertility rates $f_{s,t}$, mortality rates $\rho_{s,t}$, and immigration rates $i_{s,t}$ in Sections {ref}`SecDemogFert`, {ref}`SecDemogMort`, and {ref}`SecDemogImm`.
(SecDemogFert)=
## Fertility rates
- In `OG-ZAF`, we assume that the fertility rates for each age cohort $f_s$ are constant across time. However, this assumption is conceptually straightforward to relax. Our data for South Africa fertility rates by age come from United Nations fertility rate data for a country for some range of years (at least one year) and by age. The country_id=710 is for South Africa. These data come from the United Nations Data Portal API for UN population data (see https://population.un.org/dataportal/about/dataapi). The UN variable code for Population by 1-year age groups and sex is "47" and that for Fertility rates by age of mother (1-year) is "68".
+ Our data for South Africa fertility rates by age come from United Nations fertility rate data for a country for some range of years (at least one year) and by age. The country_id=710 is for South Africa. These data come from the United Nations Data Portal API for UN population data (see https://population.un.org/dataportal/about/dataapi). The UN variable code for Population by 1-year age groups and sex is "47" and that for Fertility rates by age of mother (1-year) is "68".
{numref}`Figure %s ` was created using the [`ogcore.demographics.get_fert()`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/demographics.py#L146) function, which downloaded the data from the United National Data Portal API and plotted it in Python.[^un_data_portal]
```{code-cell} ipython3
:tags: ["hide-input", "remove-output"]
-
- import numpy as np
- import matplotlib.pyplot as plt
+ import os
import ogcore.demographics as demog
+ plot_path = os.path.join(os.path.abspath(''), 'images')
- fert_rates = demog.get_fert(
+ fert_rates, fig = demog.get_fert(
totpers=100,
min_age=0,
max_age=99,
country_id="710",
- start_year=2023,
- end_year=2023,
- graph=False,
+ start_year=YEAR_TO_PLOT,
+ end_year=YEAR_TO_PLOT,
+ graph=True,
plot_path=None,
download_path=None,
)
- plt.plot(
- np.arange(1, 101), np.squeeze(fert_rates), linestyle="-", linewidth=1,
- color="black"
- )
- plt.scatter(
- np.arange(1, 101), np.squeeze(fert_rates), color="red", marker="o", s=4
- )
- plt.grid(
- visible=True, which='major', axis='both', color='0.5', linestyle='--',
- linewidth=0.3
- )
- plt.xlabel(r"Age ($s$)")
- plt.ylabel(r"Fertility rate ($f_s$)")
- plt.savefig('fert_rates_zaf.png')
+ plt.savefig(os.path.join(plot_path, "fert_rates.png"), dpi=300)
plt.show()
```
- ```{figure} ./images/fert_rates_zaf.png
+ ```{figure} ./images/fert_rates.png
---
height: 400px
name: FigFertRatesZAF
@@ -106,75 +92,63 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r
(SecDemogMort)=
## Mortality rates
- The mortality rates in our model $\rho_s$ are a one-period hazard rate and represent the probability of dying within one year, given that an household is alive at the beginning of the period in which they are age-$s$. We assume that the mortality rates for each age cohort $\rho_s$ are constant across time. But this assumption can be relaxed. These data come from the United Nations Population Data Portal API for UN population data (see https://population.un.org/dataportal/about/dataapi). The model uses neonatal mortality rates (deaths per 1,000 live births, divided by 1,000) for the infant mortality rate from World Bank World Development Indicators, available at https://data.worldbank.org/indicator/SH.DYN.NMRT
+ The mortality rates in our model $\rho_{s,t}$ are a one-period hazard rate and represent the probability of dying within one year, given that an household is alive at the beginning of the period in which they are age-$s$. These data come from the United Nations Population Data Portal API for UN population data (see https://population.un.org/dataportal/about/dataapi). The model uses neonatal mortality rates (deaths per 1,000 live births, divided by 1,000) for the infant mortality rate from World Bank World Development Indicators, available at https://data.worldbank.org/indicator/SH.DYN.NMRT
The mortality rates are a population-weighted average of the male and female mortality rates by one-year age increments reported by United Nations. The maximum age in years in our model is truncated to 100-years old. In addition, we constrain the mortality rate to be 1.0 or 100 percent at the maximum age of 100.
```{code-cell} ipython3
:tags: ["hide-input", "remove-output"]
- import numpy as np
import matplotlib.pyplot as plt
+ import os
import ogcore.demographics as demog
- mort_rates, inf_mort_rate = demog.get_mort(
+ plot_path = os.path.join(os.path.abspath(''), 'images')
+ mort_rates, _, fig = demog.get_mort(
totpers=100,
min_age=0,
max_age=99,
country_id="710",
- start_year=2023,
- end_year=2023,
- graph=False,
+ start_year=YEAR_TO_PLOT,
+ end_year=YEAR_TO_PLOT,
+ graph=True,
plot_path=None,
download_path=None,
)
-
- plt.plot(
- np.arange(0, 101), np.hstack((inf_mort_rate, np.squeeze(mort_rates))),
- linestyle="-", linewidth=1, color="black"
- )
- plt.scatter(
- np.arange(0, 101), np.hstack((inf_mort_rate, np.squeeze(mort_rates))),
- color="red", marker="o", s=4
- )
- plt.grid(
- visible=True, which='major', axis='both', color='0.5', linestyle='--',
- linewidth=0.3
- )
plt.xlabel(r"Age ($s$)")
plt.ylabel(r"Mortality rate ($\rho_s$)")
- plt.savefig('mort_rates_zaf.png')
+ plt.savefig(os.path.join(plot_path, "mort_rates.png"), dpi=300)
plt.show()
```
- ```{figure} ./images/mort_rates_zaf.png
+ ```{figure} ./images/mort_rates.png
---
height: 400px
name: FigMortRatesZAF
---
- South Africa mortility rates by age $\left(\rho_s\right)$ for $E+S=100$: year 2023
+ South Africa mortality rates by age $\left(\rho_{s,t}\right)$ for $E+S=100$: year 2023
```
(SecDemogImm)=
## Immigration rates
- Because of the difficulty in getting accurate immigration rate data by age, we estimate the immigration rates by age in our model $i_s$ as the average residual that reconciles the current-period population distribution with next period's population distribution given fertility rates $f_s$ and mortality rates $\rho_s$. Solving equations {eq}`EqPopLawofmotion` for the immigration rate $i_s$ gives the following characterization of the immigration rates in given population levels in any two consecutive periods $\omega_{s,t}$ and $\omega_{s,t+1}$ and the fertility rates $f_s$ and mortality rates $\rho_s$.
+ Because of the difficulty in getting accurate immigration rate data by age, we estimate the immigration rates by age in our model $i_s$ as the average residual that reconciles the current-period population distribution with next period's population distribution given fertility rates $f_s$ and mortality rates $\rho_{s,t}$. Solving equations {eq}`EqPopLawofmotion` for the immigration rate $i_s$ gives the following characterization of the immigration rates in given population levels in any two consecutive periods $\omega_{s,t}$ and $\omega_{s,t+1}$ and the fertility rates $f_s$ and mortality rates $\rho_{s,t}$.
```{math}
:label: EqPopImmRates
- i_1 &= \frac{\omega_{1,t+1} - (1 - \rho_0)\sum_{s=1}^{E+S}f_s\omega_{s,t}}{\omega_{1,t}}\quad\forall t \\
- i_{s+1} &= \frac{\omega_{s+1,t+1} - (1 - \rho_s)\omega_{s,t}}{\omega_{s+1,t}}\qquad\qquad\forall t\quad\text{and}\quad 1\leq s \leq E+S-1
+ i_{1,t} &= \frac{\omega_{1,t+1} - (1 - \rho_{0,t})\sum_{s=1}^{E+S}f_{s,t}\omega_{s,t}}{\omega_{1,t}}\quad\forall t \\
+ i_{s+1,t+1} &= \frac{\omega_{s+1,t+1} - (1 - \rho_{s,t})\omega_{s,t}}{\omega_{s+1,t}}\qquad\qquad\forall t\quad\text{and}\quad 1\leq s \leq E+S-1
```
```{code-cell} ipython3
:tags: ["hide-input", "remove-output"]
-
- import numpy as np
+ import os
import matplotlib.pyplot as plt
import ogcore.demographics as demog
+ plot_path = os.path.join(os.path.abspath(''), 'images')
- imm_rates = demog.get_imm_rates(
+ imm_rates, fig = demog.get_imm_rates(
totpers=100,
min_age=0,
max_age=99,
@@ -183,51 +157,37 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r
infmort_rates=None,
pop_dist=None,
country_id="710",
- start_year=2023,
- end_year=2023,
- graph=False,
+ start_year=YEAR_TO_PLOT,
+ end_year=YEAR_TO_PLOT + 50,
+ graph=True,
plot_path=None,
download_path=None,
)
-
- plt.plot(
- np.arange(1, 101), np.squeeze(imm_rates), linestyle="-", linewidth=1,
- color="black"
- )
- plt.scatter(
- np.arange(1, 101), np.squeeze(imm_rates), color="red", marker="o", s=4
- )
- plt.grid(
- visible=True, which='major', axis='both', color='0.5', linestyle='--',
- linewidth=0.3
- )
- plt.xlabel(r"Age ($s$)")
- plt.ylabel(r"Immigration rate ($i_s$)")
- plt.savefig('imm_rates_zaf.png')
+ plt.savefig(os.path.join(plot_path, "imm_rates.png"), dpi=300)
plt.show()
```
- ```{figure} ./images/imm_rates_zaf.png
+ ```{figure} ./images/imm_rates.png
---
height: 400px
name: FigImmRatesZAF
---
- South Africa immigration rates by age $\left(\rho_s\right)$ for $E+S=100$: year 2023
+ South Africa immigration rates by age $\left(i_s\right)$ for $E+S=100$: year 2023
```
- We calculate our immigration rates for the consecutive-year-periods of population distribution data 2022 and 2023. The immigration rates $i_s$ that we use in our model are the the residuals described in {eq}`EqPopImmRates` implied by these two consecutive periods. {numref}`Figure %s ` shows the estimated immigration rates for $E+S=100$ and given the fertility rates from Section {ref}`SecDemogFert` and the mortality rates from Section {ref}`SecDemogMort`. These immigration rates show large out-migration from South Africa.[^out_migration]
+ We calculate our immigration rates for the consecutive-year-periods of population distribution data 2022 and 2023. The immigration rates $i_{s,t}$ that we use in our model are the the residuals described in {eq}`EqPopImmRates` implied by these two consecutive periods. {numref}`Figure %s ` shows the estimated immigration rates for $E+S=100$ and given the fertility rates from Section {ref}`SecDemogFert` and the mortality rates from Section {ref}`SecDemogMort`. These immigration rates show large out-migration from South Africa.[^out_migration]
At the end of Section {ref}`SecDemogPopSSTP`, we describe a small adjustment that we make to the immigration rates after a certain number of periods in order to make computation of the transition path equilibrium of the model compute more robustly.
(SecDemogPopSSTP)=
## Population steady-state and transition path
- This model requires information about mortality rates $\rho_s$ in order to solve for the household's problem each period. It also requires the steady-state stationary population distribution $\bar{\omega}_{s}$ and population growth rate $\bar{g}_n$ as well as the full transition path of the stationary population distribution $\hat{\omega}_{s,t}$ and population grow rate $\tilde{g}_{n,t}$ from the current state to the steady-state. To solve for the steady-state and the transition path of the stationary population distribution, we write the stationary population dynamic equations {eq}`EqPopLawofmotionStat` and their matrix representation {eq}`EqPopLOMstatmat`.
+ This model requires information about mortality rates $\rho_{s,t}$ in order to solve for the household's problem each period. It also requires the steady-state stationary population distribution $\bar{\omega}_{s}$ and population growth rate $\bar{g}_n$ as well as the full transition path of the stationary population distribution $\hat{\omega}_{s,t}$ and population grow rate $\tilde{g}_{n,t}$ from the current state to the steady-state. To solve for the steady-state and the transition path of the stationary population distribution, we write the stationary population dynamic equations {eq}`EqPopLawofmotionStat` and their matrix representation {eq}`EqPopLOMstatmat`.
```{math}
:label: EqPopLawofmotionStat
- \hat{\omega}_{1,t+1} &= \frac{(1-\rho_0)\sum_{s=1}^{E+S} f_s\hat{\omega}_{s,t} + i_1\hat{\omega}_{1,t}}{1+\tilde{g}_{n,t+1}}\quad\forall t \\
- \hat{\omega}_{s+1,t+1} &= \frac{(1 - \rho_s)\hat{\omega}_{s,t} + i_{s+1}\hat{\omega}_{s+1,t}}{1+\tilde{g}_{n,t+1}}\qquad\quad\:\forall t\quad\text{and}\quad 1\leq s \leq E+S-1
+ \hat{\omega}_{1,t+1} &= \frac{(1-\rho_{0,t})\sum_{s=1}^{E+S} f_{s,t}\hat{\omega}_{s,t} + i_{1,t}\hat{\omega}_{1,t}}{1+\tilde{g}_{n,t+1}}\quad\forall t \\
+ \hat{\omega}_{s+1,t+1} &= \frac{(1 - \rho_{s,t})\hat{\omega}_{s,t} + i_{s+1,t}\hat{\omega}_{s+1,t}}{1+\tilde{g}_{n,t+1}}\qquad\quad\:\forall t\quad\text{and}\quad 1\leq s \leq E+S-1
```
```{math}
@@ -345,7 +305,7 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r
The most recent year of population data come from {cite}`Census:2015` population estimates for both sexes for 2013. We those data and use the population transition matrix {eq}`EqPopLOMstatmat2` to age it to the current model year of 2015. We then use {eq}`EqPopLOMstatmat2` to generate the transition path of the population distribution over the time period of the model. {numref}`Figure %s ` shows the progression from the 2013 population data to the fixed steady-state at period $t=120$. The time path of the growth rate of the economically active population $\tilde{g}_{n,t}$ is shown in {numref}`Figure %s `.
- ```{figure} ./images/PopDistPath.png
+ ```{figure} ./images/pop_distribution.png
---
height: 500px
name: FigPopDistPath
@@ -353,7 +313,35 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r
Exogenous stationary population distribution at periods along transition path
```
- ```{figure} ./images/GrowthPath.png
+ ```{code-cell} ipython3
+ :tags: ["hide-input", "remove-output"]
+ import os
+ import ogcore.demographics as demog
+ import matplotlib.pyplot as plt
+ YEAR_TO_PLOT = 2023
+ plot_path = os.path.join(os.path.abspath(''), 'images')
+ fig = pp.plot_pop_growth(
+ p,
+ start_year=YEAR_TO_PLOT,
+ num_years_to_plot=150,
+ include_title=False,
+ path=None,
+ )
+ # Add average growth rate with this
+ plt.plot(
+ np.arange(YEAR_TO_PLOT, YEAR_TO_PLOT + 150),
+ np.ones(150) * np.mean(p.g_n[:150]),
+ linestyle="-",
+ linewidth=1,
+ color="red",
+ )
+ plt.xlabel(r"Model Period ($t$)")
+ plt.ylabel(r"Population Growth Rate ($g_{n,t}$)")
+ plt.savefig(os.path.join(plot_path, "population_growth_rates.png"), dpi=300)
+ plt.show()
+ ```
+
+ ```{figure} ./images/population_growth_rates.png
---
height: 500px
name: FigGrowthPath
diff --git a/_sources/content/calibration/earnings.md b/_sources/content/calibration/earnings.md
index a499bdb..c78266d 100644
--- a/_sources/content/calibration/earnings.md
+++ b/_sources/content/calibration/earnings.md
@@ -14,9 +14,9 @@ kernelspec:
(Chap_LfEarn)=
# Lifetime Earnings Profiles
-Among households in `OG-ZAF`, we model both age heterogeneity and within-age ability heterogeneity. We use this ability or productivity heterogeneity to generate the income heterogeneity that we see in the data.
+Among households in `OG-ZAF`, we model variations in the labor productivity over the lifecycle and between households of different skill groups. Together, these variations in productivity generate a distribution of earnings that is calibrated to match the level of inequality in South Africa. This chapter describes the calibration of the lifecycle earnings profiles and the distribution of earnings in the model.
-Differences among workers' productivity in terms of ability is one of the key dimensions of heterogeneity to model in a micro-founded macroeconomy. In this chapter, we characterize this heterogeneity as deterministic lifetime productivity paths to which new cohorts of agents in the model are randomly assigned. In `OG-ZAF`, households' labor income comes from the equilibrium wage and the agent's endogenous quantity of labor supply. In this section, we augment the labor income expression with an individual productivity $e_{j,s}$, where $j$ is the index of the ability type or path of the individual and $s$ is the age of the individual with that ability path.
+Differences among workers' productivity is one of the key dimensions of heterogeneity to model in a micro-founded macroeconomy. In this chapter, we characterize this heterogeneity as deterministic lifetime productivity paths to which new cohorts of agents in the model are randomly assigned. In `OG-ZAF`, households' labor income comes from the equilibrium wage and the agent's endogenous quantity of labor supply. In this section, we augment the labor income expression with an individual productivity $e_{j,s}$, where $j$ is the index of the ability type or path of the individual and $s$ is the age of the individual with that ability path.
```{math}
:label: EqLaborIncome
@@ -25,13 +25,14 @@ Differences among workers' productivity in terms of ability is one of the key di
In this specification, $w_t$ is an equilibrium wage representing a portion of labor income that is common to all workers. Individual quantity of labor supply is $n_{j,s,t}$, and $e_{j,s}$ represents a labor productivity factor that augments or diminishes the productivity of a worker's labor supply relative to average productivity.
-We calibrate deterministic ability paths such that each lifetime income group has a different life-cycle profile of earnings. The distribution of income and wealth are often focal components of macroeconomic models. These calibrations require the use of microeconomic data on household incomes, but this level of data is not readily available for South Africa from public sources or surveys. To overcome this, we start with the proposition that estimated productivity curves calibrated for the [OG-USA](https://pslmodels.github.io/OG-USA/content/calibration/earnings.html) model, generated from micro-level earnings data, represent a generalized relationship between age and lifetime income {cite}`DeBackerEtAl:2017`. As such, our objective is to generate the curves for the U.S. and then adjust their generalized shapes to produce those for South Africa. In other words, our strategic approach is to apply South Africa's national distribution of income by age to the estimated U.S. data on income by age, and then use these re-distributed data to re-estimate the earning profile curves and use them for South Africa. This is done in two ways (in this order):
+We calibrate the model such that each lifetime income group has a different life-cycle profile of earnings. Since the distribution on income and wealth are key aspects of our model, we calibrate these processes so that we can represent earners in the top 1 percent of the distribution of lifetime income.
+
+We calibrate deterministic productivity paths such that each lifetime income group has a different life-cycle profile of earnings. The distribution of income and wealth are often focal components of macroeconomic models. These calibrations require the use of microeconomic data on household incomes, but this level of data is not readily available for South Africa from public sources or surveys. To overcome this, we start with the proposition that estimated productivity curves calibrated for the [OG-USA](https://pslmodels.github.io/OG-USA/content/calibration/earnings.html) model, generated from micro-level earnings data, represent a generalized relationship between age and lifetime income {cite}`DeBackerEtAl:2017`. As such, our objective is to generate the curves for the U.S. and then adjust their generalized shapes to produce those for South Africa. In other words, our strategic approach is to begin with the lifecycle labor productivity profiles estimated from detailed U.S. data and then adjust these to match the distribution of income in South Africa. This is done in two ways (in this order):
+
1. Adjustment by income ($J$): adjust the gaps between the U.S. curves to match South Africa's distribution between the $J$-income groups, using data from the World Inequality Database (WID);
2. Adjustment by age ($S$): adjust the shape of all the U.S. curves to match South Africa's national distribution of income per capita for each age year, using data from the National Transfer Accounts database (NTA).
-The data for the U.S. come from the U.S. Internal Revenue Services's (IRS) Statistics of Income program (SOI) Continuous Work History Sample (CWHS). {cite}`DeBackerEtAl:2017` match the SOI data with Social Security Administration (SSA) data on age and Current Population Survey (CPS) data on hours in order to generate a non-top-coded measure of hourly wage. See {cite}`DeBackerRamnath:2017` for a detailed description of the methodology.
-
-```{figure} ./images/ability_log_2D_ZAF.png
+```{figure} ./images/ability_profiles.png
---
height: 350px
name: FigLogAbil
@@ -39,174 +40,6 @@ name: FigLogAbil
Exogenous life cycle income ability paths $\log(e_{j,s})$ with $S=80$ and $J=7$
```
-
-
-
{numref}`Figure %s ` shows a calibration for $J=7$ deterministic lifetime ability paths $e_{j,s}$ corresponding to labor income percentiles $\boldsymbol{\lambda}=[0.25, 0.25, 0.20, 0.10, 0.10, 0.09, 0.01]$. Because there are few individuals above age 80 in the data, {cite}`DeBackerEtAl:2017` extrapolate these estimates for model ages 80-100 using an arctan function.
-We calibrate the model such that each lifetime income group has a different life-cycle profile of earnings. Since the distribution on income and wealth are key aspects of our model, we calibrate these processes so that we can represent earners in the top 1 percent of the distribution of lifetime income.
-
-
-(SecLFearnLifInc)=
-## Lifetime Income
-
- In our model, labor supply and savings, and thus lifetime income, are endogenous. We therefore define lifetime income as the present value of lifetime labor endowments and not the value of lifetime labor earnings. Note that our data are at the tax filing unit. We take this unit to be equivalent to a household. Because of differences in household structure (i.e., singles versus couples), our definition of lifetime labor income will be in per-adult terms. In particular, for filing units with a primary and secondary filer, our imputed wage represents the average hourly earnings between the two. When calculating lifetime income, we assign single- and couple-households the same labor endowment. This has the effect of making our lifetime income metric a per-adult metric; there is therefore not an over-representation of couple-households in the higher lifetime income groups simply because their time endowment is higher than for singles. We use the following approach to measure the lifetime income.
-
- First, since our panel data do not allow us to observe the complete life cycle of earnings for each household (because of sample attrition, death or the finite sample period of the data), we use an imputation to estimate wages in the years of the household's economic life for which they do not appear in the CWHS. To do this, we estimate the following equation, separately by household type (where household types are single male, single female, couple with male head, or couple with female head):
-
- ```{math}
- :label: wage_step1
- ln(w_{i,t}) = \alpha_{i} + \beta_{1}age_{i,t} + \beta_{2}age_{i,t}^{2} + \beta_{3}*age_{i,t}^{3} + \varepsilon_{i,t}
- ```
-
- The parameter estimates, including the household fixed effects, from Equation {eq}`wage_step1` are shown in {numref}`TabWage_step1`. These estimates are then used to impute values for log wages in years of each households' economic life for which we do not have data. This creates a balanced panel of log wages of households with heads aged 21 to 80. The actual and imputed wage values are then used to calculate the net present value of lifetime labor endowments per adult for each household. Specifically, we define lifetime income for household $i$ as:
-
- ```{math}
- :label: eqn:LI
- LI_{i} = \sum_{t=21}^{80}\left(\frac{1}{1+r}\right)^{t-21}(w_{i,t}*4000)
- ```
-
-
- ```{list-table} **Initial log wage regressions.** Source: CWHS data, 1991-2009. \*\* Significant at the 5-percent level ($p<0.05$). \*\*\* Significant at the 1-percent level ($p<0.01$).
- :header-rows: 1
- :name: TabWage_step1
- * - Dependent variable
- - Single male
- - Single female
- - Married male head
- - Married female head
- * - $Age$
- - 0.177\*\*\*
- - 0.143\*\*\*
- - 0.134\*\*\*
- - 0.065\*\*
- * -
- - (0.006)
- - (0.005)
- - (0.004)
- - (0.027)
- * - $Age^2$
- - -0.003\*\*\*
- - -0.002\*\*\*
- - -0.002\*\*\*
- - -0.000
- * -
- - (0.000)
- - (0.000)
- - (0.000)
- - (0.001)
- * - $Age^3$
- - 0.000\*\*\*
- - 0.000\*\*\*
- - 0.000\*\*\*
- - 0.000
- * -
- - (0.000)
- - (0.000)
- - (0.000)
- - (0.000)
- * - $Constant$
- - -0.839\*\*\*
- - -0.648\*\*\*
- - -0.042
- - 1.004\*\*\*
- * -
- - (0.072)
- - (0.070)
- - (0.058)
- - (0.376)
- * - Adjusted $R^2$
- - -0.007
- - 0.011
- - -0.032
- - -0.324
- * - Observations
- - 88,833
- - 96,670
- - 141,564
- - 6,314
- ```
-
-
- Note that households all have the same time endowment in each year (4000 hours). Thus, the amount of the time endowment scales the lifetime income up or down, but does not change the lifetime income of one household relative to another. This is not the case with the interest rate, $r$, which we fix at 4\%. Changes in the interest rate differentially impact the lifetime income calculation for different individuals because they may face different earnings profiles. For example, a higher interest rate would reduced the discounted present value of lifetime income for those individuals whose wage profiles peaked later in their economic life by a larger amount than it would reduce the discounted present value of lifetime income for individuals whose wage profiles peaked earlier.
-
-
-## Profiles by Lifetime Income
-
- With observations of lifetime income for each household, we next sort households and find the percentile of the lifetime income distribution within which each household falls. With these percentiles, we create our lifetime income groupings.
- ```{math}
- :label: EqLfEarnLambda_j
- \lambda_{j}=[0.25, 0.25, 0.2, 0.1, 0.1, 0.09, 0.01]
- ```
-
- That is, lifetime income group one includes those in below the 25th percentile, group two includes those from the 25th to the median, group three includes those from the median to the 70th percentile, group four includes those from the 70th to the 80th percentile, group 5 includes those from the 80th to 90th percentile, group 6 includes those from the 90th to 99th percentile, and group 7 consists of the top one percent in the lifetime income distribution.
-
-
- To get a life-cycle profile of effective labor units for each group, we estimate the wage profile for each lifetime income group. We do this by estimating the following regression model Equation {eq}`` separately for each lifetime income group using data on actual (not imputed) wages:
-
- ```{math}
- :label: EqWage_profile
- ln(w_{j,t}) = \alpha_{j} + \beta_{1}age_{j,t} + \beta_{2}age_{j,t}^{2} + \beta_{3}*age_{j,t}^{3} + \varepsilon_{j,t}
- ```
-
- Life-cycle earnings profiles are implied by the parameters estimated from equation {eq}`EqWage_profile`, and are used to plot {numref}`Figure %s `. The arctan function used to extrapolate these estimates for model ages 80-100 takes the following form:
- ```{math}
- :label: EqLfEarnArctan
- y = \left(\frac{-a}{\pi}\right)*arctan(bx+c)+\frac{a}{2}
- ```
- where $x$ is age, and $a$, $b$, and $c$ are the parameters we search over for the best fit of the function to the following three criteria: 1) the value of the function should match the value of the data at age 80, 2) the slope of the arctan should match the slope of the data at age 80, and 3) the value of the function should match the value of the data at age 100 times a constant. This constant is 0.5 for all lifetime income groups, except for the second highest ability at 0.7 (otherwise, the second highest has a lower income than the third highest ability group in the last few years).
-
-
-## Income at the very top
-
-In addition to lifecycle profiles of the seven percentile groups above, the model provides calibrations of income at the very top. This includes breaking out percentiles as fine as the top 0.01% of earners. The two alternative $\lambda$ vectors are $\lambda_{j}=[0.25, 0.25, 0.2, 0.1, 0.1, 0.09, 0.005, 0.004, 0.001]$ and $\lambda_{j}=[0.25, 0.25, 0.2, 0.1, 0.1, 0.09, 0.005, 0.004, 0.0009, 0.0001]$.
-
-Because we do not have U.S. panel data that allow us to observe such top percentile groups, we make the following assumptions in calibrating income at the very top. First, we assume the shape of the lifecycle profile of these top earners is the same as the top 1% overall. Second, we use 2018 estimates from the methodology of {cite}`PikettySaez:2003` to provide factors to scale the earnings process we estimate for groups inside the top 1%.[^PS_note]
-
-
-```{list-table} **Incomes at the very top** Source: Piketty and Saez (2003) 2018 estimates.
-:header-rows: 1
-:name: tab:top_incomes
-
-* - Income Percentile Range
- - Ratio to Top 1%
- - Mean Income
-* - Top 1%
- - $1,143,687
- - 1.000
-* - Top 1-0.5%
- - $524,677
- - 0.459
-* - Top 0.5-0.1%
- - $968,991
- - 0.847
-* - Top 0.1-0.01%
- - $3,103,621
- - 2.714
-* - Top 0.1%
- - $4,937,516
- - 4.317
-* - Top 0.01%
- - $21,442,570
- - 18.749
-```
-
-
[^PS_note]: These data are available from the website of Emmanuel Saez: [https://eml.berkeley.edu/~saez/](https://eml.berkeley.edu/~saez/). We use numbers from Table0, Panel B, "Income excluding realized capital gains."
diff --git a/_sources/content/calibration/exogenous_parameters.md b/_sources/content/calibration/exogenous_parameters.md
index a82a2a3..2ec415d 100644
--- a/_sources/content/calibration/exogenous_parameters.md
+++ b/_sources/content/calibration/exogenous_parameters.md
@@ -14,9 +14,8 @@ kernelspec:
(Chap_Exog)=
# Exogenous Parameters
- [TODO: This chapter needs heavy updating. Would be nice to do something similar to API chapter. But it is also nice to have references and descriptions as in the table below.]
- In this chapter, list the exogenous inputs to the model, options, and where the values come from (weak calibration vs. strong calibration). Point to the respective chapters for some of the inputs. Mention the code in [`ogzaf_default_parameters.json`](https://github.com/EAPD-DRB/OG-ZAF/blob/master/ogzaf/ogzaf_default_parameters.json) in the OG-ZAF repository and in [`default_parameters.json`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/default_parameters.json) and [`parameters.py`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/parameters.py) in the OG-Core repository.
+ The JSON file [`ogzaf_default_parameters.json`](https://github.com/EAPD-DRB/OG-ZAF/blob/master/ogzaf/ogzaf_default_parameters.json) provides values for all the model parameters used as defaults for `OG-ZAF`. Below, we provide a table highlighting some of the parameters describing the scale of the model (number of periods, aged, productivity types) and some parameters of the solution method (dampening parameter for TPI). The table below provides a list of the exogenous parameters and their baseline calibration values.