[1]
import numpy as np
from scipy import optimize
from scipy import interpolate
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
plt.style.use('seaborn-whitegrid')
colors = [x['color'] for x in plt.style.library['seaborn']['axes.prop_cycle']]
from matplotlib import cm
Linear regression
[2]
def DGP(N):
""" data generating process
Args:
N (int): number of observations
Returns:
x1 (ndarray): independent variable x1
x2 (ndarray): independent variable x2
y (ndarray): dependent varialbe y
"""
# a. independent variables
x1 = np.random.normal(0,1,size=N)
x2 = np.random.normal(0,1,size=N)
# b. errors
eps = np.random.normal(0,1,size=N)
extreme = np.random.uniform(0,1,size=N)
eps[extreme < 0.05] += np.random.normal(-5,1,size=N)[extreme < 0.05]
eps[extreme > 0.95] += np.random.normal(5,1,size=N)[extreme > 0.95]
# d. depenent variable
y = 0.1 + 0.3*x1 + 0.5*x2 + eps
return x1, x2, y
[3]
np.random.seed(2020)
x1,x2,y = DGP(10000)
Question 1
[4]
def OLS_estimate(x1,x2,y):
""" compute OLS estimates using matrix algebra
Args:
x1 (ndarray): independent variable x1
x2 (ndarray): independent variable x2
y (ndarray): dependent varialbe y
Returns:
betas (ndrarray): estimates
"""
X = np.vstack((np.ones(x1.size),x1,x2)).T
betas = (np.linalg.inv(X.T@X)@X.T)@y
return betas
betas = OLS_estimate(x1,x2,y)
for i,beta in enumerate(betas):
print(f'beta{i} = {beta:.4f}')
beta0 = 0.0957
beta1 = 0.2929
beta2 = 0.5033
Question 2
[5]
fig = plt.figure()
ax = fig.add_subplot(1,1,1,projection='3d')
# a. predicted
x1v = np.linspace(x1.min(),x1.max(),100)
x2v = np.linspace(x2.min(),x2.max(),100)
x1g, x2g = np.meshgrid(x1v,x2v,indexing='ij')
yhat = betas[0] + betas[1]*x1g + betas[2]*x2g
ax.plot_wireframe(x1g,x2g,yhat,color='black',alpha=0.1);
# b. scatter
ax.scatter(x1,x2,y,s=50,edgecolor='black',facecolor=colors[0],alpha=0.5);
# c. details
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$y$')
ax.invert_xaxis()
fig.tight_layout()
Question 3
[6]
def OLS_objective(betas,x1,x2,y):
""" OLS objective
Args:
betas (ndarray): current guess on parameters
x1 (ndarray): independent variable x1
x2 (ndarray): independent variable x2
y (ndarray): dependent varialbe y
Returns:
ressum (ndrarray): sum of squared residuals
"""
yhat = betas[0] + betas[1]*x1 + betas[2]*x2
ressum = np.sum((yhat-y)**2)
return ressum
def OLS_estimate_alt(x1,x2,y):
""" compute OLS estimates using numerical optimizer
Args:
x1 (ndarray): independent variable x1
x2 (ndarray): independent variable x2
y (ndarray): dependent varialbe y
Returns:
betas (ndarray): parameter estimates
"""
betas0 = np.array([0.1,0.3,0.5])
sol = optimize.minimize(OLS_objective,betas0,args=(x1,x2,y),method='Nelder-Mead')
betas = sol.x
return betas
[7]
betas = OLS_estimate_alt(x1,x2,y)
for i,beta in enumerate(betas):
print(f'beta{i} = {beta:.4f}')
beta0 = 0.0957
beta1 = 0.2929
beta2 = 0.5033
Question 4
[8]
def LAD_objective(betas,x1,x2,y):
""" LAD objective
Args:
betas (ndarray): current guess on parameters
x1 (ndarray): independent variable x1
x2 (ndarray): independent variable x2
y (ndarray): dependent varialbe y
Returns:
ressum (ndrarray): sum of absolute residuals
"""
yhat = betas[0] + betas[1]*x1 + betas[2]*x2
ressum = np.sum(np.abs(yhat-y))
return ressum
def LAD_estimate(x1,x2,y):
""" compute LAD estimates using numerical optimizer
Args:
x1 (ndarray): independent variable x1
x2 (ndarray): independent variable x2
y (ndarray): dependent varialbe y
Returns:
betas (ndarray): parameter estimates
"""
betas0 = np.array([0.1,0.3,0.5])
sol = optimize.minimize(LAD_objective,betas0,args=(x1,x2,y),method='Nelder-Mead')
betas = sol.x
return betas
[9]
betas = LAD_estimate(x1,x2,y)
for i,beta in enumerate(betas):
print(f'beta{i} = {beta:.4f}')
beta0 = 0.0921
beta1 = 0.3074
beta2 = 0.5116
Question 5
[10]
# a. setup
K = 5000
N = 50
# b. allocate
betas_OLS = np.empty((K,3))
betas_LAD = np.empty((K,3))
# c. estimate
for k in range(K):
x1,x2,y = DGP(N)
betas_OLS[k,:] = OLS_estimate(x1,x2,y)
betas_LAD[k,:] = LAD_estimate(x1,x2,y)
Mean and std.:
[11]
for i in range(3):
print(f'beta{i}')
print(f' OLS: mean = {np.mean(betas_OLS[:,i]):.4f}, std. = {np.std(betas_OLS[:,i]):.4f}')
print(f' LAD: mean = {np.mean(betas_LAD[:,i]):.4f}, std. = {np.std(betas_LAD[:,i]):.4f}')
beta0
OLS: mean = 0.1026, std. = 0.2749
LAD: mean = 0.1025, std. = 0.2032
beta1
OLS: mean = 0.3019, std. = 0.2844
LAD: mean = 0.3027, std. = 0.2134
beta2
OLS: mean = 0.5003, std. = 0.2860
LAD: mean = 0.5008, std. = 0.2116
Histograms:
[12]
fig = plt.figure(figsize=(12,4))
for i in range(3):
ax = fig.add_subplot(1,3,i+1)
ax.hist(betas_OLS[:,i],bins=100,alpha=0.5,label='OLS');
ax.hist(betas_LAD[:,i],bins=100,alpha=0.5,label='LAD');
ax.set_title(f'$\\beta_{{{i}}}$')
ax.legend(frameon=True)
fig.tight_layout()
Durable consumption
[13]
# a. parameters
rho = 2
alpha = 0.8
chi = 0.9
beta = 0.96
r = 0.04
Delta = 0.25
# b. grids
d_vec = np.linspace(1e-8,5,100)
m1_vec = np.linspace(1e-8,10,100)
m2_vec = np.linspace(0.5,10,100)
Question 1
[14]
def utility(c,d,x,rho,alpha,chi):
""" utility
Args:
c (float): non-durable consumption
d (float): pre-commited durable consumption
x (float): extra durable consumption
rho (float): CRRA parameter
alpha (float): utility weight on non-durable consumption
chi (float): scala factor for extra durable consumption
Returns:
(float): utility of consumption
"""
return (c**alpha*(d+chi*x)**(1-alpha))**(1-rho)/(1-rho)
def v2(c,m2,d,rho,alpha,chi):
""" value of choice in period 2
Args:
c (float): non-durable consumption
m2 (float): cash-on-hand in beginning of period 2
d (float): pre-commited durable consumption
x (float): extra durable consumption
rho (float): CRRA parameter
alpha (float): utility weight on non-durable consumption
chi (float): scala factor for extra durable consumption
Returns:
(float): value-of-choice
"""
x = m2-c
return utility(c,d,x,rho,alpha,chi)
[15]
def solve_period_2(m2_vec,d_vec,rho,alpha,chi):
""" solve consumer problem in period 2
Args:
m2 (ndarray): vector of cash-on-hand in beginning of period 2
d (ndarray): vector of pre-commited durable consumption
d (float): pre-commited durable consumption
x (float): extra durable consumption
rho (float): CRRA parameter
alpha (float): utility weight on non-durable consumption
chi (float): scala factor for extra durable consumption
Returns:
v2_mat (ndarray): value function in period 2
c_ast_mat (ndarray): consumption function
x_ast_mat (ndarray): implied extra durable consumption function
"""
# a. allocate
v2_mat = np.empty((m2_vec.size,d_vec.size))
c_ast_mat = np.empty((m2_vec.size,d_vec.size))
x_ast_mat = np.empty((m2_vec.size,d_vec.size))
# b. loop over states
for i,m2 in enumerate(m2_vec):
for j,d in enumerate(d_vec):
# i. objective
obj = lambda c: -v2(c,m2,d,rho,alpha,chi)
# ii. initial value (consume half)
x0 = m2/2
# iii. optimizer
result = optimize.minimize_scalar(obj,x0,method='bounded',bounds=[1e-8,m2])
# iv. save
v2_mat[i,j] = -result.fun
c_ast_mat[i,j] = result.x
x_ast_mat[i,j] = m2-c_ast_mat[i,j]
return v2_mat,c_ast_mat,x_ast_mat
[16]
v2_mat,c_ast_mat,x_ast_mat = solve_period_2(m2_vec,d_vec,rho,alpha,chi)
[17]
for ystr,y in [('value',v2_mat),('c',c_ast_mat),('x',x_ast_mat)]:
fig = plt.figure()
ax = fig.add_subplot(1,1,1,projection='3d')
# a. value function
m2g,dg = np.meshgrid(m2_vec,d_vec,indexing='ij')
ax.plot_surface(m2g,dg,y,cmap=cm.jet)
# b. details
ax.set_title(ystr)
ax.set_xlabel('$m_1$')
ax.set_ylabel('$d$')
ax.set_zlabel('')
ax.invert_xaxis()
fig.tight_layout()
Questions 2
[18]
v2_interp = interpolate.RegularGridInterpolator([m2_vec,d_vec], v2_mat,
bounds_error=False,fill_value=None)
[19]
def w(a,d,r,Delta,v2_interp):
""" post-decision value function in period 1
Args:
a (float): end-of-period asset
d (float): pre-commited durable consumption
r (float): return on savings
Delta (float): income risk scale factor
v2_interp (RegularGridInterpolator): interpolator for value function in period 2
Returns:
(ndarray): discounted post-decision value
"""
# a. initialize
w = 0
y = np.array([1-Delta,1,1+Delta])
# b. loop over shocks
for i in range(3):
m2_now = (1+r)*a + y[i]
v2_now = v2_interp([m2_now,d])[0]
w += 1/3*v2_now
return beta*w
[20]
def v1(d,m1,beta,r,Delta,v2_interp):
""" post-decision value function in period 1
Args:
d (float): pre-commited durable consumption
m1 (float): cash-on-hand in the beginning of period 1
beta (float): discount factor
r (float): return on savings
Delta (float): income risk scale factor
v2_interp (RegularGridInterpolator): interpolator for value function in period 2
Returns:
(ndarray): value-of-choice
"""
a = m1-d
return w(a,d,r,Delta,v2_interp)
[21]
def solve_period_1(m1_vec,beta,r,Delta,v2_interp):
""" post-decision value function in period 1
Args:
m1 (ndarray): vector cash-on-hand in the beginning of period 1
beta (float): discount factor
r (float): return on savings
Delta (float): income risk scale factor
v2_interp (RegularGridInterpolator): interpolator for value function in period 2
Returns:
v1_vec (ndarray): value function in period 1
d_ast_vec (ndarray): pre-commited durable consumption function
"""
# a. grids
v1_vec = np.empty(m1_vec.size)
d_ast_vec = np.empty(m1_vec.size)
# b. solve for each m1 in grid
for i,m1 in enumerate(m1_vec):
# i. objective
obj = lambda x: -v1(x[0],m1,beta,r,Delta,v2_interp)
# ii. initial guess (pre-commit half)
x0 = m1*1/3
# iii. optimize
result = optimize.minimize(obj,[x0],method='L-BFGS-B',bounds=((1e-8,m1),))
# iv. save
v1_vec[i] = -result.fun
d_ast_vec[i] = result.x
return v1_vec,d_ast_vec
[22]
v1_vec,d_ast_vec = solve_period_1(m1_vec,beta,r,Delta,v2_interp)
[23]
fig = plt.figure(figsize=(10,4))
ax = fig.add_subplot(1,2,1)
ax.plot(m1_vec,d_ast_vec)
ax.set_xlabel('$m_1$')
ax.set_ylabel('$d$')
ax.set_title('pre-committed durable consumption')
ax = fig.add_subplot(1,2,2)
ax.plot(m1_vec,v1_vec)
ax.set_xlabel('$m_1$')
ax.set_ylabel('$v_1$')
ax.set_title('value function in period 1')
fig.tight_layout()
Question 3
[24]
Lambda = 0.2
m0_vec = np.linspace(1e-8,6,100)
d0_vec = np.linspace(1e-8,3,100)
[25]
v1_interp = interpolate.RegularGridInterpolator([m1_vec], v1_vec,
bounds_error=False,fill_value=None)
[26]
z = np.empty((m0_vec.size,d_vec.size))
for i,m0 in enumerate(m0_vec):
for j,d0 in enumerate(d0_vec):
# a. no adjustment
v_keep = w(m0,d0,r,Delta,v2_interp)
# b. adjustment
v_adj = v1_interp([m0+(1-Lambda)*d0])[0]
# c. best
z[i,j] = 0 if v_keep > v_adj else 1
[27]
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
m0g,d0g = np.meshgrid(m0_vec,d0_vec,indexing='ij')
ax.scatter(m0g[z == 0],d0g[z == 0],s=4,label='keep')
ax.scatter(m0g[z == 1],d0g[z == 1],s=4,label='adj')
ax.set_xlim([m0_vec[0],m0_vec[-1]])
ax.set_ylim([d0_vec[0],d0_vec[-1]])
ax.set_xlabel('$m_0$')
ax.set_ylabel('$d_0$')
ax.legend(frameon=True)
fig.tight_layout()
Gradient descent
Question 1
[28]
def gradient_descent(f,x0,epsilon=1e-6,Theta=0.1,Delta=1e-8,max_iter=10_000):
""" minimize function with gradient descent algorithm
Args:
f (callable): function
x0 (np.ndarray): initial guess
eps (float,optional): tolerance
Theta (float,optional): initial step-size
Delta (float,optional): step-size in numerical derivatives
max_iter (int,optional): maximum number of iterations
Returns:
x (ndarray): minimum
it (int): number of iterations used
"""
# a. initialize
x = x0
fx = f(x0)
# b. iterate
it = 0
while it < max_iter:
# i. jacobian
fp = np.empty(x0.size)
for i in range(x0.size):
x_ = x.copy()
x_[i] = x[i] + Delta
fp[i] = (f(x_)-fx)/Delta
# ii. check convergence
if np.max(np.abs(fp)) < epsilon: break
# ii. line search
theta = Theta
while it < max_iter:
# o. x value
x_theta = x - theta*fp
# oo. new function value
fx_theta = f(x_theta)
it += 1
# ooo. break or continue line search
if fx_theta < fx:
fx = fx_theta
x = x_theta
break
else:
theta /= 2
return x,it
[29]
def rosen(x):
return (1.0-x[0])**2+2*(x[1]-x[0]**2)**2
x0 = np.array([1.1,1.1])
try:
x,it = gradient_descent(rosen,x0)
print(f'minimum found at ({x[0]:.4f},{x[1]:.4f}) after {it} iterations')
assert np.allclose(x,[1,1])
except:
print('not implemented yet')
minimum found at (1.0000,1.0000) after 331 iterations