Skip to content

Commit 186bcab

Browse files
committed
Updated SMM.md
1 parent 943ebdd commit 186bcab

File tree

2 files changed

+218
-22
lines changed

2 files changed

+218
-22
lines changed

docs/book/struct_est/SMM.md

Lines changed: 218 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ import numpy as np
339339
import scipy.stats as sts
340340
import requests
341341
import matplotlib.pyplot as plt
342+
from mpl_toolkits.mplot3d import Axes3D
342343
343344
344345
# Define function that generates values of a normal pdf
@@ -442,6 +443,8 @@ name: FigSMM_EconScoreTruncNorm
442443
Macroeconomic midterm scores and three truncated normal distributions
443444
```
444445

446+
447+
(SecSMM_CodeExmp_MacrTest_2mI)=
445448
#### Two moments, identity weighting matrix
446449
Let's try estimating the parameters $\mu$ and $\sigma$ from the truncated normal distribution by SMM, assuming that we know the cutoff values for the distribution of scores $c_{lb}=0$ and $c_{ub}=450$. What moments should we use? Let's try the mean and variance of the data. These two statistics of the data are defined by:
447450

@@ -844,8 +847,8 @@ def criterion(params, *args):
844847
mu = scalar, mean of the normally distributed random variable
845848
sigma = scalar > 0, standard deviation of the normally
846849
distributed random variable
847-
args = length 5 tuple,
848-
(xvals, unif_vals, cut_lb, cut_ub, W_hat)
850+
args = length 6 tuple,
851+
(xvals, unif_vals, cut_lb, cut_ub, W_hat, simple)
849852
xvals = (N,) vector, values of the truncated normally
850853
distributed random variable
851854
unif_vals = (N, S) matrix, matrix of draws from U(0,1) distribution.
@@ -859,6 +862,8 @@ def criterion(params, *args):
859862
distribution. Values below this cutoff have zero
860863
probability
861864
W_hat = (R, R) matrix, estimate of optimal weighting matrix
865+
simple = Boolean, =True if error vec is simple difference,
866+
=False if error vec is percent difference
862867
863868
OTHER FUNCTIONS AND FILES CALLED BY THIS FUNCTION:
864869
err_vec2()
@@ -874,9 +879,9 @@ def criterion(params, *args):
874879
--------------------------------------------------------------------
875880
'''
876881
mu, sigma = params
877-
xvals, unif_vals, cut_lb, cut_ub, W_hat = args
882+
xvals, unif_vals, cut_lb, cut_ub, W_hat, simple = args
878883
err = err_vec2(xvals, unif_vals, mu, sigma, cut_lb, cut_ub,
879-
simple=False)
884+
simple)
880885
crit_val = err.T @ W_hat @ err
881886
882887
return crit_val
@@ -895,7 +900,7 @@ mean_mod = mean_sim.mean()
895900
var_mod = var_sim.mean()
896901
err_vec2(data, unif_vals_2, mu_test, sig_test, cut_lb, cut_ub, simple=False)
897902
crit_test = criterion(np.array([mu_test, sig_test]), data, unif_vals_2,
898-
0.0, 450.0, np.eye(2))
903+
0.0, 450.0, np.eye(2), False)
899904
print("Average of mean test scores across simulations is:", mean_mod)
900905
print("")
901906
print("Average variance of test scores across simulations is:", var_mod)
@@ -912,7 +917,7 @@ mu_init_1 = 300
912917
sig_init_1 = 30
913918
params_init_1 = np.array([mu_init_1, sig_init_1])
914919
W_hat1_1 = np.eye(2)
915-
smm_args1_1 = (data, unif_vals_2, cut_lb, cut_ub, W_hat1_1)
920+
smm_args1_1 = (data, unif_vals_2, cut_lb, cut_ub, W_hat1_1, False)
916921
results1_1 = opt.minimize(criterion, params_init_1, args=(smm_args1_1),
917922
method='L-BFGS-B',
918923
bounds=((1e-10, None), (1e-10, None)))
@@ -940,7 +945,7 @@ print("Results from scipy.opmtimize.minimize:")
940945
print(results1_1)
941946
```
942947

943-
Let's plot the PDF implied by these SMM estimates $(\hat{\mu}_{SMM},\hat{\sigma}_{SMM})=(612.337, 197.264)$ against the histogram of the data in {numref}`Figure %s <FigSMM_EconScoreSMM1>` below.
948+
Let's plot the PDF implied by these SMM estimates $(\hat{\mu}_{SMM},\hat{\sigma}_{SMM})=(612.337, 197.264)$ against the histogram of the data in {numref}`Figure %s <FigSMM_Econ381_SMM1>` below.
944949

945950
```{code-cell} ipython3
946951
:tags: ["remove-output"]
@@ -965,22 +970,22 @@ plt.show()
965970
```{figure} ../../../images/smm/Econ381scores_smm1.png
966971
---
967972
height: 500px
968-
name: FigSMM_EconScoreSMM1
973+
name: FigSMM_Econ381_SMM1
969974
---
970975
SMM-estimated PDF function and data histogram, 2 moments, identity weighting matrix, Econ 381 scores (2011-2012)
971976
```
972977

973-
That looks just like the maximum likelihood estimate from the {ref}`Chap_MaxLikeli` chapter. Let's see what the criterion function looks like for different values of $\mu$ and $\sigma$.
978+
That looks just like the maximum likelihood estimate from the {ref}`Chap_MaxLikeli` chapter. {numref}`Figure %s <FigSMM_Econ381_crit1>` below shows what the minimizer is doing. The figure shows the criterion function surface for different of $\mu$ and $\sigma$ in the truncated normal distribution. The minimizer is searching for the parameter values that give the lowest criterion function value.
974979

975980
```{code-cell} ipython3
976-
:tags: []
981+
:tags: ["remove-output"]
977982
978-
mu_vals = np.linspace(60, 700, 50)
979-
sig_vals = np.linspace(20, 250, 50)
980-
crit_vals = np.zeros((50, 50))
981-
crit_args = (data, unif_vals_2, cut_lb, cut_ub, W_hat1_1)
982-
for mu_ind in range(50):
983-
for sig_ind in range(50):
983+
mu_vals = np.linspace(60, 700, 90)
984+
sig_vals = np.linspace(20, 250, 100)
985+
crit_vals = np.zeros((90, 100))
986+
crit_args = (data, unif_vals_2, cut_lb, cut_ub, W_hat1_1, False)
987+
for mu_ind in range(90):
988+
for sig_ind in range(100):
984989
crit_params = np.array([mu_vals[mu_ind], sig_vals[sig_ind]])
985990
crit_vals[mu_ind, sig_ind] = criterion(crit_params, *crit_args)[0][0]
986991
@@ -989,19 +994,210 @@ mu_mesh, sig_mesh = np.meshgrid(mu_vals, sig_vals)
989994
crit_SMM1_1 = criterion(np.array([mu_SMM1_1, sig_SMM1_1]), *crit_args)[0][0]
990995
991996
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
992-
ax.plot_surface(sig_mesh, mu_mesh, crit_vals, rstride=8,
993-
cstride=1, cmap=cmap1)
994-
ax.scatter(sig_SMM1_1, mu_SMM1_1, crit_SMM1_1, color='red', marker='o',
995-
s=10, label='SMM1 estimate')
997+
ax.plot_surface(mu_mesh.T, sig_mesh.T, crit_vals, rstride=8,
998+
cstride=1, cmap=cmap1, alpha=0.9)
999+
ax.scatter(mu_SMM1_1, sig_SMM1_1, crit_SMM1_1, color='red', marker='o',
1000+
s=18, label='SMM1 estimate')
9961001
ax.view_init(elev=12, azim=30, roll=0)
9971002
ax.set_title('Criterion function for values of mu and sigma')
998-
ax.set_xlabel(r'$\sigma$')
999-
ax.set_ylabel(r'$\mu$')
1003+
ax.set_xlabel(r'$\mu$')
1004+
ax.set_ylabel(r'$\sigma$')
10001005
ax.set_zlabel(r'Crit. func.')
1006+
plt.tight_layout()
10011007
10021008
plt.show()
10031009
```
10041010

1011+
```{figure} ../../../images/smm/Econ381_crit1.png
1012+
---
1013+
height: 500px
1014+
name: FigSMM_Econ381_crit1
1015+
---
1016+
Criterion function surface for values of $\mu$ and $\sigma$ for SMM estimation of truncated normal with two moments and identity weighting matrix (SMM estimate shown as red dot)
1017+
```
1018+
1019+
Let's compute the SMM estimator for the variance-covariance matrix $\hat{\Sigma}_{SMM}$ of our SMM estimates $\hat{\theta}_{SMM}$ using the equation in Section {ref}`SecSMM_VarCovTheta` based on the Jacobian $d(\tilde{x},x|\hat{\theta}_{SMM})$ of the moment error vector $e(\tilde{x},x|\hat{\theta}_{SMM})$ from the criterion function at the estimated (optimal) parameter values $\hat{\theta}_{SMM}$. We first write a function that computes the Jacobian matrix $d(x|\hat{\theta}_{SMM})$, which has shape $2\times 2$ in this case with two moments $R=2$.
1020+
1021+
```{code-cell} ipython3
1022+
:tags: []
1023+
1024+
def Jac_err2(data_vals, unif_vals, mu, sigma, cut_lb, cut_ub, simple=False):
1025+
'''
1026+
This function computes the Jacobian matrix of partial derivatives of the
1027+
R x 1 moment error vector e(x|theta) with respect to the K parameters
1028+
theta_i in the K x 1 parameter vector theta. The resulting matrix is R x K
1029+
Jacobian.
1030+
'''
1031+
Jac_err = np.zeros((2, 2))
1032+
h_mu = 1e-4 * mu
1033+
h_sig = 1e-4 * sigma
1034+
Jac_err[:, 0] = (
1035+
(err_vec2(xvals, unif_vals, mu + h_mu, sigma, cut_lb, cut_ub, simple) -
1036+
err_vec2(xvals, unif_vals, mu - h_mu, sigma, cut_lb, cut_ub, simple)) /
1037+
(2 * h_mu)
1038+
).flatten()
1039+
Jac_err[:, 1] = (
1040+
(err_vec2(xvals, unif_vals, mu, sigma + h_sig, cut_lb, cut_ub, simple) -
1041+
err_vec2(xvals, unif_vals, mu, sigma - h_sig, cut_lb, cut_ub, simple)) /
1042+
(2 * h_sig)
1043+
).flatten()
1044+
1045+
return Jac_err
1046+
```
1047+
1048+
```{code-cell} ipython3
1049+
:tags: []
1050+
1051+
S = unif_vals_2.shape[1]
1052+
d_err2 = Jac_err2(data, unif_vals_2, mu_SMM1_1, sig_SMM1_1, 0.0, 450.0, False)
1053+
print("Jacobian matrix of derivatives of moment error functions is:")
1054+
print(d_err2)
1055+
print("")
1056+
print("Weighting matrix W is:")
1057+
print(W_hat1_1)
1058+
SigHat2 = (1 / S) * lin.inv(d_err2.T @ W_hat1_1 @ d_err2)
1059+
print("")
1060+
print("Variance-covariance matrix of estimated parameter vector is:")
1061+
print(SigHat2)
1062+
print("")
1063+
print('Std. err. mu_hat=', np.sqrt(SigHat2[0, 0]))
1064+
print('Std. err. sig_hat=', np.sqrt(SigHat2[1, 1]))
1065+
```
1066+
1067+
This SMM estimation methodology of estimating $\mu$ and $\sigma$ from the truncated normal distribution to fit the distribution of Econ 381 test scores using two moments from the data and using the identity matrix as the optimal weighting matrix is not very precise. The standard errors for the estimates of $\hat{mu}$ and $\hat{sigma}$ are bigger than their values.
1068+
1069+
In the next section, we see if we can get more accurate estimates (lower criterion function values) of $\hat{mu}$ and $\hat{sigma}$ with more precise standard errors by using the two-step optimal weighting matrix described in Section {ref}`SecSMM_W_2step`.
1070+
1071+
1072+
(SecSMM_CodeExmp_MacrTest_2m2st)=
1073+
#### Two moments, two-step optimal weighting matrix
1074+
Similar to the maximum likelihood estimation problem in Chapter {ref}`Chap_MaxLikeli`, it looks like the minimum value of the criterion function shown in {numref}`Figure %s <FigSMM_Econ381_crit1>` is roughly equal for a specific portion increase of $\mu$ and $\sigma$ together. That is, the estimation problem with these two moments probably has a correspondence of values of $\mu$ and $\sigma$ that give roughly the same minimum criterion function value. This issue has two possible solutions.
1075+
1076+
1. Maybe we need the two-step variance covariance estimator to calculate a "more" optimal weighting matrix $W$.
1077+
2. Maybe our two moments aren't very good moments for fitting the data.
1078+
1079+
Let's first try the two-step weighting matrix.
1080+
1081+
```{code-cell} ipython3
1082+
:tags: []
1083+
1084+
def get_Err_mat2(pts, unif_vals, mu, sigma, cut_lb, cut_ub, simple=False):
1085+
'''
1086+
--------------------------------------------------------------------
1087+
This function computes the R x S matrix of errors from each
1088+
simulated moment for each moment error. In this function, we have
1089+
hard coded R = 2.
1090+
--------------------------------------------------------------------
1091+
INPUTS:
1092+
xvals = (N,) vector, test scores data
1093+
unif_vals = (N, S) matrix, uniform random variables that generate
1094+
the N observations of simulated data for S simulations
1095+
mu = scalar, mean of the normally distributed random variable
1096+
sigma = scalar > 0, standard deviation of the normally
1097+
distributed random variable
1098+
cut_lb = scalar or string, ='None' if no cutoff is given,
1099+
otherwise is scalar lower bound value of distribution.
1100+
Values below this value have zero probability
1101+
cut_ub = scalar or string, ='None' if no cutoff is given,
1102+
otherwise is scalar upper bound value of distribution.
1103+
Values above this value have zero probability
1104+
simple = boolean, =True if errors are simple difference, =False
1105+
if errors are percent deviation from data moments
1106+
1107+
OTHER FUNCTIONS AND FILES CALLED BY THIS FUNCTION:
1108+
model_moments()
1109+
1110+
OBJECTS CREATED WITHIN FUNCTION:
1111+
R = integer = 2, hard coded number of moments
1112+
S = integer >= R, number of simulated datasets
1113+
Err_mat = (R, S) matrix, error by moment and simulated data
1114+
mean_model = scalar, mean value from model
1115+
var_model = scalar > 0, variance from model
1116+
1117+
FILES CREATED BY THIS FUNCTION: None
1118+
1119+
RETURNS: Err_mat
1120+
--------------------------------------------------------------------
1121+
'''
1122+
R = 2
1123+
S = unif_vals.shape[1]
1124+
Err_mat = np.zeros((R, S))
1125+
mean_data, var_data = data_moments2(pts)
1126+
sim_vals = trunc_norm_draws(unif_vals, mu, sigma, cut_lb, cut_ub)
1127+
mean_model, var_model = data_moments2(sim_vals)
1128+
if simple:
1129+
Err_mat[0, :] = mean_model - mean_data
1130+
Err_mat[1, :] = var_model - var_data
1131+
else:
1132+
Err_mat[0, :] = (mean_model - mean_data) / mean_data
1133+
Err_mat[1, :] = (var_model - var_data) / var_data
1134+
1135+
return Err_mat
1136+
```
1137+
1138+
```{code-cell} ipython3
1139+
:tags: []
1140+
1141+
Err_mat2 = get_Err_mat2(data, unif_vals_2, mu_SMM1_1, sig_SMM1_1, 0.0, 450.0, False)
1142+
VCV2 = (1 / unif_vals_2.shape[1]) * (Err_mat2 @ Err_mat2.T)
1143+
print("2nd stage est. of var-cov matrix of moment error vec across sims:")
1144+
print(VCV2)
1145+
W_hat2_1 = lin.inv(VCV2)
1146+
print("")
1147+
print("2nd state est. of optimal weighting matrix:")
1148+
print(W_hat2_1)
1149+
```
1150+
1151+
```{code-cell} ipython3
1152+
:tags: []
1153+
1154+
params_init2_1 = np.array([mu_SMM1_1, sig_SMM1_1])
1155+
smm_args2_1 = (data, unif_vals_2, cut_lb, cut_ub, W_hat2_1, False)
1156+
results2_1 = opt.minimize(criterion, params_init2_1, args=(smm_args2_1),
1157+
method='L-BFGS-B',
1158+
bounds=((1e-10, None), (1e-10, None)))
1159+
mu_SMM2_1, sig_SMM2_1 = results2_1.x
1160+
print('mu_SMM2_1=', mu_SMM2_1, ' sig_SMM2_1=', sig_SMM2_1)
1161+
```
1162+
1163+
Look at how much smaller (more efficient) the estimated standard errors are in this case with the two-step optimal weighting matrix $\hat{W}_{2step}$.
1164+
1165+
```{code-cell} ipython3
1166+
:tags: []
1167+
1168+
d_err2_2 = Jac_err2(data, unif_vals_2, mu_SMM2_1, sig_SMM2_1, 0.0, 450.0, False)
1169+
print("Jacobian matrix of derivatives of moment error functions is:")
1170+
print(d_err2_2)
1171+
print("")
1172+
print("Weighting matrix W is:")
1173+
print(W_hat2_1)
1174+
SigHat2_2 = (1 / S) * lin.inv(d_err2_2.T @ W_hat2_1 @ d_err2_2)
1175+
print("")
1176+
print("Variance-covariance matrix of estimated parameter vector is:")
1177+
print(SigHat2_2)
1178+
print("")
1179+
print('Std. err. mu_hat=', np.sqrt(SigHat2_2[0, 0]))
1180+
print('Std. err. sig_hat=', np.sqrt(SigHat2_2[1, 1]))
1181+
```
1182+
1183+
1184+
(SecSMM_CodeExmp_MacrTest_4mI)=
1185+
#### Four moments, identity matrix weighting matrix
1186+
1187+
Using a better weighting matrix didn't improve our estimates or fit very much---the estimates of $\hat{mu}$ and $\hat{\sigma}$ and the corresponding minimum criterion function value. But it did improve our standard errors. But even with the optimal weighting matrix, our standard errors still look pretty big. This might mean that we did not choose good moments for fitting the data. Let's try some different moments. How about four moments to match.
1188+
1189+
1. The percent of observations greater than 430 (between 430 and 450)
1190+
2. The percent of observations between 320 and 430
1191+
3. The percent of observations between 220 and 320
1192+
4. The percent of observations less than 220 (between 0 and 220)
1193+
1194+
This means we are using four moments $R=4$ to identify two paramters $\mu$ and $\sigma$ ($K=2$). This problem is now overidentified ($R>K$). This is often a desired approach for SMM estimation.
1195+
1196+
1197+
(SecSMM_CodeExmp_MacrTest_4m2st)=
1198+
#### Four moments, two-step optimal weighting matrix
1199+
1200+
10051201

10061202
(SecSMM_CodeExmp_BM72)=
10071203
### Brock and Mirman (1972) estimation by SMM

images/smm/Econ381_crit1.png

358 KB
Loading

0 commit comments

Comments
 (0)