├── 10 └── 10_marriage_divorce.pdf ├── 11 └── 11_FertilityFamilyLabor.pdf ├── 12 ├── 01. Dynamic Labor Model_12.ipynb ├── 12_HamishLow_CEBI_PhD_Lecture.pdf └── DynLaborModel_12.py ├── 13 ├── 13_time_allocation.pdf ├── CES timeuse.pdf ├── DynamicSpecialization.ipynb └── DynamicSpecializationModel.py ├── 2023 ├── 10 │ └── 10_marriage_divorce.pdf ├── 11 │ └── 11_FertilityFamilyLabor.pdf ├── 12 │ └── 12_HamishLow_CEBI_PhD_Lecture.pdf ├── 13 │ ├── 13_time_allocation.pdf │ └── CES timeuse.pdf ├── 01 │ ├── .ipynb_checkpoints │ │ ├── 01. Introduction to EconModel and consav-checkpoint.ipynb │ │ └── 02. Consumption-Saving Model-checkpoint.ipynb │ ├── 01. Introduction to EconModel and consav.ipynb │ ├── 02. Consumption-Saving Model.ipynb │ ├── 1_Introduction.pdf │ ├── ConSavModel.py │ ├── __pycache__ │ │ └── ConSavModel.cpython-38.pyc │ └── saved │ │ └── example_copy.p ├── 02 │ ├── .ipynb_checkpoints │ │ ├── 01. StructuralEstimation-checkpoint.ipynb │ │ ├── 2_structural_estimation-checkpoint.pdf │ │ └── BufferStockModel-checkpoint.py │ ├── 01. StructuralEstimation.ipynb │ ├── 2_structural_estimation.pdf │ ├── BufferStockModel.py │ ├── __pycache__ │ │ └── BufferStockModel.cpython-38.pyc │ └── objective.pdf ├── 03 │ ├── .ipynb_checkpoints │ │ ├── 01. DynamicLabor-checkpoint.ipynb │ │ ├── 01. StaticLabor-checkpoint.ipynb │ │ └── 02. DynamicLabor-checkpoint.ipynb │ ├── 01. StaticLabor.ipynb │ ├── 02. DynamicLabor.ipynb │ └── 3_labor_supply.pdf ├── 04 │ ├── .ipynb_checkpoints │ │ ├── 01. Dynamic Labor Model-checkpoint.ipynb │ │ └── 02. Consumption-Saving Model-checkpoint.ipynb │ ├── 01. Dynamic Labor Model.ipynb │ ├── 4_human_capital.pdf │ ├── DynLaborModel.py │ └── __pycache__ │ │ └── DynLaborModel.cpython-38.pyc ├── 05 │ ├── .ipynb_checkpoints │ │ └── 01. Dynamic Labor w Children-checkpoint.ipynb │ ├── 01. Dynamic Labor w Children.ipynb │ ├── 5_career_and_children.pdf │ ├── DynLaborFertModel.py │ └── __pycache__ │ │ └── DynLaborFertModel.cpython-38.pyc ├── 06 │ ├── 01. Dynamic Household Labor.ipynb │ ├── 6_household_labor_taxes.pdf │ ├── DynHouseholdLaborModel.py │ └── __pycache__ │ │ └── DynHouseholdLaborModel.cpython-38.pyc ├── 07 │ ├── 01. Dynamic Household Labor, Transfers.ipynb │ └── 7_household_labor_transfers.pdf ├── 08 │ ├── 01 Bargaining.ipynb │ ├── 8_household_models.pdf │ ├── Bargaining.py │ ├── UserFunctions.py │ └── __pycache__ │ │ ├── Bargaining.cpython-38.pyc │ │ └── UserFunctions.cpython-38.pyc ├── 09 │ ├── 01 Bargaining.ipynb │ ├── 9_divorce_law.pdf │ ├── Bargaining.py │ ├── UserFunctions.py │ └── __pycache__ │ │ ├── Bargaining.cpython-38.pyc │ │ └── UserFunctions.cpython-38.pyc ├── Assignments │ ├── 01 │ │ └── Assignment 1.pdf │ ├── 02 │ │ └── Assignment 2.pdf │ └── 03 │ │ └── Assignment 3.pdf └── Exam │ ├── DynamicSpecialization.ipynb │ ├── DynamicSpecializationModel.py │ ├── DynamicSpecializationModel_post.py │ ├── DynamicSpecialization_post.ipynb │ └── HouseholdBehavior_exam.pdf ├── 2024 ├── 10 │ └── 10_marriage_divorce.pdf ├── 11 │ └── 11_FertilityFamilyLabor.pdf ├── 12 │ └── 12_HamishLow_CEBI_PhD_Lecture.pdf ├── 13 │ ├── 13_time_allocation.pdf │ ├── CES timeuse.pdf │ ├── DynamicSpecialization.ipynb │ └── DynamicSpecializationModel.py ├── 01 │ ├── 01. Introduction to EconModel and consav.ipynb │ ├── 02. Consumption-Saving Model.ipynb │ ├── 1_Introduction.pdf │ ├── ConSavModel.py │ └── saved │ │ └── example_copy.p ├── 02 │ ├── 01. StructuralEstimation.ipynb │ ├── 2_structural_estimation.pdf │ ├── BufferStockModel.py │ └── objective.pdf ├── 03 │ ├── 01. StaticLabor.ipynb │ ├── 02. DynamicLabor.ipynb │ └── 3_labor_supply.pdf ├── 04 │ ├── 01. Dynamic Labor Model.ipynb │ ├── 4_human_capital.pdf │ └── DynLaborModel.py ├── 05 │ ├── 01. Dynamic Labor w Children.ipynb │ ├── 5_career_and_children.pdf │ └── DynLaborFertModel.py ├── 06 │ ├── 01. Dynamic Household Labor.ipynb │ ├── 6_household_labor_taxes.pdf │ └── DynHouseholdLaborModel.py ├── 07 │ ├── 01. Dynamic Household Labor, Transfers.ipynb │ └── 7_household_labor_transfers.pdf ├── 07_expost │ ├── 01. Dynamic Household Labor, Transfers.ipynb │ ├── 7_household_labor_transfers.pdf │ └── DynHouseholdLaborModel.py ├── 08 │ ├── 01 Bargaining.ipynb │ ├── 8_household_models.pdf │ ├── Bargaining.py │ └── UserFunctions.py ├── 09 │ ├── 01 Bargaining.ipynb │ ├── 9_divorce_law.pdf │ ├── Bargaining.py │ └── UserFunctions.py ├── 11_expost │ ├── 01. Dynamic Labor w Children.ipynb │ └── DynLaborFertModel.py ├── 12_expost │ ├── Assignment2.ipynb │ └── DynHouseholdLaborModel.py ├── Assignments │ ├── 01 │ │ └── Assignment 1.pdf │ ├── 02 │ │ └── Assignment 2.pdf │ └── 03 │ │ └── Assignment 3.pdf └── Exam │ ├── DynLaborModel.py │ ├── DynLaborModel_post.py │ ├── Exam2024.ipynb │ ├── Exam2024_post.ipynb │ └── HouseholdBehavior_exam.pdf ├── .gitignore ├── 01 ├── .ipynb_checkpoints │ ├── 01. Introduction to EconModel and consav-checkpoint.ipynb │ └── 02. Consumption-Saving Model-checkpoint.ipynb ├── 01. Introduction to EconModel and consav.ipynb ├── 02. Consumption-Saving Model.ipynb ├── 1_Introduction.pdf ├── ConSavModel.py ├── __pycache__ │ └── ConSavModel.cpython-38.pyc └── saved │ └── example_copy.p ├── 02 ├── .ipynb_checkpoints │ ├── 01. StructuralEstimation-checkpoint.ipynb │ ├── 2_structural_estimation-checkpoint.pdf │ └── BufferStockModel-checkpoint.py ├── 01. StructuralEstimation.ipynb ├── 2_structural_estimation.pdf ├── BufferStockModel.py ├── __pycache__ │ └── BufferStockModel.cpython-38.pyc └── objective.pdf ├── 03 ├── .ipynb_checkpoints │ ├── 01. DynamicLabor-checkpoint.ipynb │ ├── 01. StaticLabor-checkpoint.ipynb │ └── 02. DynamicLabor-checkpoint.ipynb ├── 01. StaticLabor.ipynb ├── 02. DynamicLabor.ipynb └── 3_labor_supply.pdf ├── 04 ├── .ipynb_checkpoints │ ├── 01. Dynamic Labor Model-checkpoint.ipynb │ └── 02. Consumption-Saving Model-checkpoint.ipynb ├── 01. Dynamic Labor Model.ipynb ├── 4_human_capital.pdf ├── DynLaborModel.py └── __pycache__ │ └── DynLaborModel.cpython-38.pyc ├── 05 ├── .ipynb_checkpoints │ └── 01. Dynamic Labor w Children-checkpoint.ipynb ├── 01. Dynamic Labor w Children.ipynb ├── 5_career_and_children.pdf ├── DynLaborFertModel.py └── __pycache__ │ └── DynLaborFertModel.cpython-38.pyc ├── 06 ├── 01. Dynamic Household Labor.ipynb ├── 6_household_labor_taxes.pdf ├── DynHouseholdLaborModel.py └── __pycache__ │ └── DynHouseholdLaborModel.cpython-38.pyc ├── 07 ├── 01. Dynamic Household Labor, Transfers.ipynb └── 7_household_labor_transfers.pdf ├── 08 ├── 01 Bargaining.ipynb ├── 8_household_models.pdf ├── Bargaining.py ├── UserFunctions.py └── __pycache__ │ ├── Bargaining.cpython-38.pyc │ └── UserFunctions.cpython-38.pyc ├── 09 ├── 01 Bargaining.ipynb ├── 9_divorce_law.pdf ├── Bargaining.py ├── UserFunctions.py └── __pycache__ │ ├── Bargaining.cpython-38.pyc │ └── UserFunctions.cpython-38.pyc ├── Assignments ├── 01 │ └── Assignment 1.pdf ├── 02 │ └── Assignment 2.pdf └── 03 │ └── Assignment 3.pdf ├── README.md ├── Thesis ideas.pdf └── open_jupyter.bat /.gitignore: -------------------------------------------------------------------------------- 1 | ##### Python template ##### 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | # data 108 | .p 109 | .npz 110 | 111 | # vscode 112 | .vscode/ 113 | 114 | ##### C++ template ##### 115 | # Prerequisites 116 | *.d 117 | 118 | # Compiled Object files 119 | *.slo 120 | *.lo 121 | *.o 122 | *.obj 123 | 124 | # Precompiled Headers 125 | *.gch 126 | *.pch 127 | 128 | # Compiled Dynamic libraries 129 | *.so 130 | *.dylib 131 | 132 | # Fortran module files 133 | *.mod 134 | *.smod 135 | 136 | # Compiled Static libraries 137 | *.lai 138 | *.la 139 | *.a 140 | *.lib 141 | 142 | # Executables 143 | *.exe 144 | *.out 145 | *.app 146 | 147 | # Ignore all _struct.cpp files 148 | *_struct.cpp 149 | 150 | # Ignore dll 151 | *.dll -------------------------------------------------------------------------------- /01/1_Introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/01/1_Introduction.pdf -------------------------------------------------------------------------------- /01/ConSavModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | 4 | from EconModel import EconModelClass, jit 5 | 6 | from consav.grids import nonlinspace 7 | from consav.linear_interp import interp_1d 8 | 9 | class ConSavModelClass(EconModelClass): 10 | 11 | def settings(self): 12 | """ fundamental settings """ 13 | 14 | pass 15 | 16 | def setup(self): 17 | """ set baseline parameters """ 18 | 19 | # unpack 20 | par = self.par 21 | 22 | par.T = 20 # time periods 23 | 24 | # preferences 25 | par.beta = 0.98 # discount factor 26 | par.rho = 2.0 # CRRA coefficient 27 | 28 | # income 29 | par.y = 1.0 # income level 30 | 31 | # saving 32 | par.r = 0.02 # interest rate 33 | 34 | # grid 35 | par.a_max = 30.0 # maximum point in wealth grid 36 | par.Na = 200 # number of grid points in wealth grid 37 | 38 | # simulation 39 | par.simT = par.T # number of periods 40 | par.simN = 1_000 # number of individuals 41 | 42 | 43 | def allocate(self): 44 | """ allocate model """ 45 | 46 | # unpack 47 | par = self.par 48 | sol = self.sol 49 | sim = self.sim 50 | 51 | # a. asset grid 52 | par.a_grid = nonlinspace(0.0,par.a_max,par.Na,1.1) 53 | 54 | # b. income 55 | par.yt = par.y * np.ones(par.T) 56 | 57 | # c. solution arrays 58 | shape = (par.T,par.Na) 59 | sol.c = np.nan + np.zeros(shape) 60 | sol.V = np.nan + np.zeros(shape) 61 | 62 | # d. simulation arrays 63 | shape = (par.simN,par.simT) 64 | sim.c = np.nan + np.zeros(shape) 65 | sim.a = np.nan + np.zeros(shape) 66 | 67 | # e. initialization 68 | sim.a_init = np.zeros(par.simN) 69 | 70 | 71 | ############ 72 | # Solution # 73 | def solve(self): 74 | 75 | # a. unpack 76 | par = self.par 77 | sol = self.sol 78 | 79 | # b. solve last period 80 | t = par.T-1 81 | sol.c[t,:] = par.a_grid + par.yt[t] 82 | sol.V[t,:] = self.util(sol.c[t,:]) 83 | 84 | # c. loop backwards [note, the last element, N, in range(N) is not included in the loop due to index starting at 0] 85 | for t in reversed(range(par.T-1)): 86 | 87 | # i. loop over state variable: wealth in beginning of period 88 | for ia,assets in enumerate(par.a_grid): 89 | 90 | # ii. find optimal consumption at this level of wealth in this period t. 91 | 92 | # objective function: negative since we minimize 93 | obj = lambda c: - self.value_of_choice(c[0],assets,t) 94 | 95 | # bounds on consumption 96 | lb = 0.000001 # avoid dividing with zero 97 | ub = assets + par.yt[t] 98 | 99 | # call optimizer 100 | if lb>=ub: # if the bounds are not feasible, set consumption to all resources 101 | sol.c[t,ia] = ub 102 | sol.V[t,ia] = -obj(sol.c[t,ia]) 103 | 104 | else: 105 | c_init = np.array(lb+0.5*ub) # initial guess on optimal consumption 106 | res = minimize(obj,c_init,bounds=((lb,ub),),method='SLSQP') 107 | 108 | # store results 109 | sol.c[t,ia] = res.x[0] 110 | sol.V[t,ia] = -res.fun 111 | 112 | 113 | def value_of_choice(self,cons,assets,t): 114 | 115 | # a. unpack 116 | par = self.par 117 | sol = self.sol 118 | 119 | # b. utility from consumption 120 | util = self.util(cons) 121 | 122 | # c. continuation value from savings 123 | V_next = sol.V[t+1] 124 | a_next = (1.0+par.r)*(assets + par.yt[t] - cons) 125 | V_next_interp = interp_1d(par.a_grid,V_next,a_next) 126 | 127 | # d. return value of choice 128 | return util + par.beta*V_next_interp 129 | 130 | 131 | def util(self,c): 132 | par = self.par 133 | 134 | return (c)**(1.0-par.rho) / (1.0-par.rho) 135 | 136 | 137 | ############## 138 | # Simulation # 139 | def simulate(self): 140 | 141 | # a. unpack 142 | par = self.par 143 | sol = self.sol 144 | sim = self.sim 145 | 146 | # b. loop over individuals and time 147 | for i in range(par.simN): 148 | 149 | # i. initialize assets 150 | sim.a[i,0] = sim.a_init[i] 151 | 152 | for t in range(par.simT): 153 | if t=(par.Nn-1)): 209 | # cannot have more children 210 | V_next_birth_interp = V_next_no_interp 211 | 212 | else: 213 | kids_next = kids + 1 214 | V_next = sol.V[t+1,kids_next] 215 | V_next_birth_interp = interp_2d(par.a_grid,par.k_grid,V_next,a_next,k_next) 216 | 217 | EV_next = par.p_birth * V_next_birth_interp + (1-par.p_birth)*V_next_no_interp 218 | 219 | # e. return value of choice (including penalty) 220 | return util + par.rho*EV_next + penalty 221 | 222 | 223 | def util(self,c,hours,kids): 224 | par = self.par 225 | 226 | beta = par.beta_0 + par.beta_1*kids 227 | 228 | return (c)**(1.0+par.eta) / (1.0+par.eta) - beta*(hours)**(1.0+par.gamma) / (1.0+par.gamma) 229 | 230 | def wage_func(self,capital,t): 231 | # after tax wage rate 232 | par = self.par 233 | 234 | return (1.0 - par.tau )* par.w_vec[t] * (1.0 + par.alpha * capital) 235 | 236 | ############## 237 | # Simulation # 238 | def simulate(self): 239 | 240 | # a. unpack 241 | par = self.par 242 | sol = self.sol 243 | sim = self.sim 244 | 245 | # b. loop over individuals and time 246 | for i in range(par.simN): 247 | 248 | # i. initialize states 249 | sim.n[i,0] = sim.n_init[i] 250 | sim.a[i,0] = sim.a_init[i] 251 | sim.k[i,0] = sim.k_init[i] 252 | 253 | for t in range(par.simT): 254 | 255 | # ii. interpolate optimal consumption and hours 256 | idx_sol = (t,sim.n[i,t]) 257 | sim.c[i,t] = interp_2d(par.a_grid,par.k_grid,sol.c[idx_sol],sim.a[i,t],sim.k[i,t]) 258 | sim.h[i,t] = interp_2d(par.a_grid,par.k_grid,sol.h[idx_sol],sim.a[i,t],sim.k[i,t]) 259 | 260 | # iii. store next-period states 261 | if t0: 120 | init_h[0] = sol.h1[t,i_k1-1,i_k2] 121 | if i_k2>0: 122 | init_h[1] = sol.h2[t,i_k1,i_k2-1] 123 | 124 | res = minimize(obj,init_h,bounds=bounds) 125 | 126 | # store results 127 | sol.h1[idx] = res.x[0] 128 | sol.h2[idx] = res.x[1] 129 | sol.V[idx] = -res.fun 130 | 131 | 132 | def value_of_choice(self,hours1,hours2,capital1,capital2,V_next): 133 | 134 | # a. unpack 135 | par = self.par 136 | 137 | # b. current utility 138 | util = self.util(hours1,hours2,capital1,capital2) 139 | 140 | # c. continuation value 141 | k1_next = (1.0-par.delta)*capital1 + hours1 142 | k2_next = (1.0-par.delta)*capital2 + hours2 143 | V_next_interp = interp_2d(par.k_grid,par.k_grid,V_next,k1_next,k2_next) 144 | 145 | # d. return value of choice 146 | return util + par.beta*V_next_interp 147 | 148 | 149 | # relevant functions 150 | def consumption(self,hours1,hours2,capital1,capital2): 151 | par = self.par 152 | 153 | income1 = self.wage_func(capital1,1) * hours1 154 | income2 = self.wage_func(capital2,2) * hours2 155 | income_hh = income1+income2 156 | 157 | if par.joint_tax: 158 | tax_hh = self.tax_func(income_hh) 159 | else: 160 | tax_hh = self.tax_func(income1) + self.tax_func(income2) 161 | 162 | return income_hh - tax_hh 163 | 164 | def wage_func(self,capital,sex): 165 | # before tax wage rate 166 | par = self.par 167 | 168 | constant = par.wage_const_1 169 | return_K = par.wage_K_1 170 | if sex>1: 171 | constant = par.wage_const_2 172 | return_K = par.wage_K_2 173 | 174 | return np.exp(constant + return_K * capital) 175 | 176 | def tax_func(self,income): 177 | par = self.par 178 | 179 | rate = 1.0 - par.tax_scale*(income**(-par.tax_pow)) 180 | return rate*income 181 | 182 | def util(self,hours1,hours2,capital1,capital2): 183 | par = self.par 184 | 185 | cons = self.consumption(hours1,hours2,capital1,capital2) 186 | 187 | util_cons = 2*(cons/2)**(1.0+par.eta) / (1.0+par.eta) 188 | util_hours1 = par.rho_1*(hours1)**(1.0+par.gamma) / (1.0+par.gamma) 189 | util_hours2 = par.rho_2*(hours2)**(1.0+par.gamma) / (1.0+par.gamma) 190 | 191 | return util_cons - util_hours1 - util_hours2 192 | 193 | ############## 194 | # Simulation # 195 | def simulate(self): 196 | 197 | # a. unpack 198 | par = self.par 199 | sol = self.sol 200 | sim = self.sim 201 | 202 | # b. loop over individuals and time 203 | for i in range(par.simN): 204 | 205 | # i. initialize states 206 | sim.k1[i,0] = sim.k1_init[i] 207 | sim.k2[i,0] = sim.k2_init[i] 208 | 209 | for t in range(par.simT): 210 | 211 | # ii. interpolate optimal hours 212 | idx_sol = t 213 | sim.h1[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h1[idx_sol],sim.k1[i,t],sim.k2[i,t]) 214 | sim.h2[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h2[idx_sol],sim.k1[i,t],sim.k2[i,t]) 215 | 216 | # store income 217 | sim.income1[i,t] = self.wage_func(sim.k1[i,t],1)*sim.h1[i,t] 218 | sim.income2[i,t] = self.wage_func(sim.k2[i,t],2)*sim.h2[i,t] 219 | 220 | # iii. store next-period states 221 | if t\n", 17 | "**Motivated** by the study “Child-Related Transfers, Household Labor Supply and Welfare” by [Guner et al. (2020)](https://academic.oup.com/restud/article/87/5/2290/5809564).
\n", 18 | "**Goal** is to replicate effects of child-related transfers.\n", 19 | "\n", 20 | "For simplicity, couples cannot divorce nor save.\n", 21 | "\n", 22 | "The **Bellman equation** and the recursive formulation of our simple model is \n", 23 | "$$\n", 24 | "\\begin{align*}\n", 25 | "V_{t}(n_{t},K_{1,t},K_{2,t}) & =\\max_{h_{1,t},h_{2,t}}U(c_{t},h_{1,t},h_{2,t},n_{t})\\\\\n", 26 | " & \\qquad\\qquad+\\beta\\mathbb{E}_{t}[V_{t+1}(n_{t+1},K_{1,t+1},K_{2,t+1})]\\\\\n", 27 | "c_{t} & =\\sum_{j=1}^{2}w_{j,t}h_{j,t}-T(w_{1,t}h_{1,t},w_{2,t}h_{2,t})\\\\\n", 28 | " & \\qquad+\\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t})\\\\\n", 29 | "n_{t+1} & =\\begin{cases}\n", 30 | "\\begin{array}{ll}\n", 31 | "n_t + 1 & \\text{with prob. } p(n_{t})\\\\\n", 32 | "n_t & \\text{with prob. } 1-p(n_{t})\n", 33 | "\\end{array}\\end{cases}\\\\\n", 34 | "\\log w_{j,t} & =\\alpha_{j,0}+\\alpha_{j,1}K_{j,t},\\;j\\in\\{1,2\\}\\\\\n", 35 | "K_{j,t+1} & =(1-\\delta)K_{j,t}+h_{j,t},\\;j\\in\\{1,2\\}\n", 36 | "\\end{align*}\n", 37 | "$$\n", 38 | "where \n", 39 | "$$\n", 40 | "p(n_t) = \\begin{cases}\n", 41 | "\\begin{array}{ll}\n", 42 | "p_n & \\text{if } n_{t}=0\\\\\n", 43 | "0 & \\text{if } n_{t}=1.\n", 44 | "\\end{array}\\end{cases}\n", 45 | "$$\n", 46 | "\n", 47 | "**Child-related transfers:** We assume that if both work, they have to buy childcare.
\n", 48 | "This means that conditional transfers are always a subsidy (cannot do 5 or 6 in taxonomy of Guner et al. (2020)).
\n", 49 | "Child-related transfers are\n", 50 | "$$\n", 51 | "\\begin{align*}\n", 52 | "\\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t}) & =\\mathcal{C}_{1}(n_{t})+\\mathcal{C}_{2}(n_{t},Y_{t})\\\\\n", 53 | " & +[\\mathcal{C}_{3}(n_{t})+\\mathcal{C}_{4}(n_{t},Y_{t})]\\cdot\\mathbf{1}(h_{1,t}\\cdot h_{2,t}>0)\n", 54 | "\\end{align*}\n", 55 | "$$\n", 56 | "where household income is\n", 57 | "$$\n", 58 | "Y_{t}=\\sum_{j=1}^{2}w_{j,t}h_{j,t}.\n", 59 | "$$\n", 60 | "\n", 61 | "The code could look something like\n", 62 | "```python\n", 63 | "def child_tran(self,hours1,hours2,income_hh,kids):\n", 64 | " par = self.par\n", 65 | " if kids<1:\n", 66 | " return 0.0\n", 67 | " \n", 68 | " else:\n", 69 | " C1 = par.uncon_uni #unconditional, universal transfer (>0)\n", 70 | " C2 = np.fmax(par.means_level - par.means_slope*income_hh , 0.0) #means-tested transfer (>0)\n", 71 | " # child-care related (net-of-subsidy costs)\n", 72 | " both_work = (hours1>0) * (hours2>0)\n", 73 | " C3 = par.cond*both_work #all working couples has this net cost (<0)\n", 74 | " C4 = par.cond_high*both_work*(income_hh>0.5) #low-income couples do not have this net-cost (<0)\n", 75 | "\n", 76 | " return C1+C2+C3+C4\n", 77 | "```\n", 78 | "\n", 79 | "**Preferences** are sum of individuals\n", 80 | "$$\n", 81 | "U(c_{t},h_{1,t},h_{2,t})=2\\frac{(c_{t}/2)^{1+\\eta}}{1+\\eta}-\\rho_{1}(n_{t})\\frac{h_{1,t}^{1+\\gamma}}{1+\\gamma}-\\rho_{2}(n_{t})\\frac{h_{2,t}^{1+\\gamma}}{1+\\gamma}\n", 82 | "$$\n", 83 | "with \n", 84 | "$$\n", 85 | "\\rho_{j}(n_{t})=\\rho_{0,j}+\\rho_{1,j}n_{t}\n", 86 | "$$\n", 87 | "\n", 88 | "**Taxes** are on the household level and child-related transfers/costs are included in taxable income\n", 89 | "$$\n", 90 | "T(\\tilde{Y})=(1-\\lambda(\\tilde{Y})^{-\\tau})\\cdot(\\tilde{Y})\n", 91 | "$$\n", 92 | "where $\\tilde{Y} = Y + \\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t})$.\n", 93 | "\n", 94 | "**Expected value** is\n", 95 | "$$\n", 96 | "\\begin{align*}\n", 97 | "\\mathbb{E}_{t}[V_{t+1}(n_{t+1},K_{1,t+1},K_{2,t+1})] & =p(n_{t})V_{t+1}(n_{t}+1,K_{1,t+1},K_{2,t+1})\\\\\n", 98 | " & +(1-p(n_{t}))V_{t+1}(n_{t},K_{1,t+1},K_{2,t+1})\n", 99 | "\\end{align*}\n", 100 | "$$\n", 101 | "\n", 102 | "**Terminal period:** There are no bequests such that\n", 103 | "$$\n", 104 | "V_{T}(n_T,K_{1,T},K_{2,T}) =\\max_{h_{1,T},h_{2,T}}U(c_{T},h_{1,T},h_{2,T})\n", 105 | "$$" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Setup" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 180, 118 | "metadata": {}, 119 | "outputs": [ 120 | { 121 | "name": "stdout", 122 | "output_type": "stream", 123 | "text": [ 124 | "The autoreload extension is already loaded. To reload it, use:\n", 125 | " %reload_ext autoreload\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "%load_ext autoreload\n", 131 | "%autoreload 2\n", 132 | "\n", 133 | "import numpy as np\n", 134 | "import numba as nb\n", 135 | "\n", 136 | "import matplotlib.pyplot as plt\n", 137 | "from mpl_toolkits import mplot3d" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "# Dual-Earner Model with Children" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "# load local model file and initialize model class\n", 154 | "# -> copy .py-module from last time and modify it!" 155 | ] 156 | }, 157 | { 158 | "attachments": {}, 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "# Simulate child-related transfers reforms" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "# modify code and run it..." 172 | ] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "Python 3", 178 | "language": "python", 179 | "name": "python3" 180 | }, 181 | "language_info": { 182 | "codemirror_mode": { 183 | "name": "ipython", 184 | "version": 3 185 | }, 186 | "file_extension": ".py", 187 | "mimetype": "text/x-python", 188 | "name": "python", 189 | "nbconvert_exporter": "python", 190 | "pygments_lexer": "ipython3", 191 | "version": "3.8.10 (default, May 19 2021, 13:12:57) [MSC v.1916 64 bit (AMD64)]" 192 | }, 193 | "toc-autonumbering": true, 194 | "vscode": { 195 | "interpreter": { 196 | "hash": "2a1ca330d9582a7d9f549c991d1ebe88efa30325a2a9c927421566fc2176e6bd" 197 | } 198 | } 199 | }, 200 | "nbformat": 4, 201 | "nbformat_minor": 4 202 | } 203 | -------------------------------------------------------------------------------- /07/7_household_labor_transfers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/07/7_household_labor_transfers.pdf -------------------------------------------------------------------------------- /08/8_household_models.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/08/8_household_models.pdf -------------------------------------------------------------------------------- /08/UserFunctions.py: -------------------------------------------------------------------------------- 1 | # set gender indication as globals 2 | woman = 1 3 | man = 2 4 | 5 | ############################ 6 | # User-specified functions # 7 | ############################ 8 | def util(c_priv,c_pub,gender,par,love=0.0): 9 | if gender == woman: 10 | rho = par.rho_w 11 | phi = par.phi_w 12 | alpha1 = par.alpha1_w 13 | alpha2 = par.alpha2_w 14 | else: 15 | rho = par.rho_m 16 | phi = par.phi_m 17 | alpha1 = par.alpha1_m 18 | alpha2 = par.alpha2_m 19 | 20 | return ((alpha1*c_priv**phi + alpha2*c_pub**phi)**(1.0-rho))/(1.0-rho) + love 21 | 22 | def resources_couple(A,par): 23 | return par.R*A + par.inc_w + par.inc_m 24 | 25 | def resources_single(A,gender,par): 26 | income = par.inc_w 27 | if gender == man: 28 | income = par.inc_m 29 | 30 | return par.R*A + income 31 | 32 | def cons_priv_single(C_tot,gender,par): 33 | # closed form solution for intra-period problem of single. 34 | if gender == woman: 35 | rho = par.rho_w 36 | phi = par.phi_w 37 | alpha1 = par.alpha1_w 38 | alpha2 = par.alpha2_w 39 | else: 40 | rho = par.rho_m 41 | phi = par.phi_m 42 | alpha1 = par.alpha1_m 43 | alpha2 = par.alpha2_m 44 | 45 | return C_tot/(1.0 + (alpha2/alpha1)**(1.0/(1.0-phi)) ) 46 | -------------------------------------------------------------------------------- /08/__pycache__/Bargaining.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/08/__pycache__/Bargaining.cpython-38.pyc -------------------------------------------------------------------------------- /08/__pycache__/UserFunctions.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/08/__pycache__/UserFunctions.cpython-38.pyc -------------------------------------------------------------------------------- /09/9_divorce_law.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/09/9_divorce_law.pdf -------------------------------------------------------------------------------- /09/UserFunctions.py: -------------------------------------------------------------------------------- 1 | # set gender indication as globals 2 | woman = 1 3 | man = 2 4 | 5 | ############################ 6 | # User-specified functions # 7 | ############################ 8 | def util(c_priv,c_pub,gender,par,love=0.0): 9 | if gender == woman: 10 | rho = par.rho_w 11 | phi = par.phi_w 12 | alpha1 = par.alpha1_w 13 | alpha2 = par.alpha2_w 14 | else: 15 | rho = par.rho_m 16 | phi = par.phi_m 17 | alpha1 = par.alpha1_m 18 | alpha2 = par.alpha2_m 19 | 20 | return ((alpha1*c_priv**phi + alpha2*c_pub**phi)**(1.0-rho))/(1.0-rho) + love 21 | 22 | def resources_couple(A,par): 23 | return par.R*A + par.inc_w + par.inc_m 24 | 25 | def resources_single(A,gender,par): 26 | income = par.inc_w 27 | if gender == man: 28 | income = par.inc_m 29 | 30 | return par.R*A + income 31 | 32 | def cons_priv_single(C_tot,gender,par): 33 | # closed form solution for intra-period problem of single. 34 | if gender == woman: 35 | rho = par.rho_w 36 | phi = par.phi_w 37 | alpha1 = par.alpha1_w 38 | alpha2 = par.alpha2_w 39 | else: 40 | rho = par.rho_m 41 | phi = par.phi_m 42 | alpha1 = par.alpha1_m 43 | alpha2 = par.alpha2_m 44 | 45 | return C_tot/(1.0 + (alpha2/alpha1)**(1.0/(1.0-phi)) ) 46 | -------------------------------------------------------------------------------- /09/__pycache__/Bargaining.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/09/__pycache__/Bargaining.cpython-38.pyc -------------------------------------------------------------------------------- /09/__pycache__/UserFunctions.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/09/__pycache__/UserFunctions.cpython-38.pyc -------------------------------------------------------------------------------- /10/10_marriage_divorce.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/10/10_marriage_divorce.pdf -------------------------------------------------------------------------------- /11/11_FertilityFamilyLabor.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/11/11_FertilityFamilyLabor.pdf -------------------------------------------------------------------------------- /12/12_HamishLow_CEBI_PhD_Lecture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/12/12_HamishLow_CEBI_PhD_Lecture.pdf -------------------------------------------------------------------------------- /12/DynLaborModel_12.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize, NonlinearConstraint 3 | import warnings 4 | warnings.filterwarnings("ignore", message="delta_grad == 0.0. Check if the approximated function is linear.") # turn of annoying warning 5 | 6 | from EconModel import EconModelClass, jit 7 | 8 | from consav.grids import nonlinspace 9 | from consav.linear_interp import interp_2d, interp_1d 10 | 11 | class DynLaborModelClass(EconModelClass): 12 | 13 | def settings(self): 14 | """ fundamental settings """ 15 | 16 | pass 17 | 18 | def setup(self): 19 | """ set baseline parameters """ 20 | 21 | # unpack 22 | par = self.par 23 | 24 | par.T = 10 # time periods 25 | 26 | # preferences 27 | par.rho = 0.98 # discount factor 28 | 29 | par.beta = 0.1 # weight on labor dis-utility 30 | par.beta_2 = 0.2 # weight on labor dis-utility for type 2 31 | par.eta = -2.0 # CRRA coefficient 32 | par.gamma = 2.5 # curvature on labor hours 33 | 34 | # income 35 | par.alpha = 0.1 # human capital accumulation 36 | par.w = 1.0 # wage base level 37 | par.tau = 0.1 # labor income tax 38 | 39 | # saving 40 | par.r = 0.02 # interest rate 41 | 42 | # health risk 43 | par.p = 0.2 # probability of human capital destruction 44 | 45 | # grids 46 | par.a_max = 5.0 # maximum point in wealth grid 47 | par.a_min = -10.0 # minimum point in wealth grid 48 | par.Na = 70 # number of grid points in wealth grid 49 | 50 | par.k_max = 20.0 # maximum point in wealth grid 51 | par.Nk = 30 # number of grid points in wealth grid 52 | 53 | par.Nm = 2 # Number of types 54 | 55 | # simulation 56 | par.simT = par.T # number of periods 57 | par.simN = 1_000 # number of individuals 58 | 59 | 60 | def allocate(self): 61 | """ allocate model """ 62 | 63 | # unpack 64 | par = self.par 65 | sol = self.sol 66 | sim = self.sim 67 | 68 | par.simT = par.T 69 | 70 | # a. asset grid 71 | par.a_grid = nonlinspace(par.a_min,par.a_max,par.Na,1.1) 72 | 73 | # b. human capital grid 74 | par.k_grid = nonlinspace(0.0,par.k_max,par.Nk,1.1) 75 | 76 | # types 77 | par.m_grid = np.arange(1,par.Nm+1) # types 78 | 79 | # c. solution arrays 80 | shape = (par.T,par.Nm,par.Na,par.Nk) 81 | sol.c = np.nan + np.zeros(shape) 82 | sol.h = np.nan + np.zeros(shape) 83 | sol.V = np.nan + np.zeros(shape) 84 | 85 | # d. simulation arrays 86 | shape = (par.simN,par.simT) 87 | sim.c = np.nan + np.zeros(shape) 88 | sim.h = np.nan + np.zeros(shape) 89 | sim.a = np.nan + np.zeros(shape) 90 | sim.k = np.nan + np.zeros(shape) 91 | 92 | # e. initialization 93 | sim.a_init = np.zeros(par.simN) 94 | sim.k_init = np.zeros(par.simN) 95 | 96 | # f. random uniform draws to simulate human capital destruction 97 | np.random.seed(9210) # for reproducibility 98 | sim.draws_uni = np.random.uniform(size=(par.simN,par.simT)) 99 | 100 | # types 101 | sim.m = np.random.randint(1,par.Nm+1,size=par.simN) # random types for each individual 102 | 103 | # f. vector of wages. Used for simulating elasticities 104 | par.w_vec = par.w * np.ones(par.T) 105 | 106 | 107 | ############ 108 | # Solution # 109 | def solve(self,do_accurate=False): 110 | 111 | # a. unpack 112 | par = self.par 113 | sol = self.sol 114 | 115 | # b. loop backwards (over all periods) 116 | for t in reversed(range(par.T)): 117 | 118 | # i. loop over state variables: human capital and wealth in beginning of period 119 | for i_m,m_type in enumerate(par.m_grid): # new state related to types of workers 120 | for i_a,assets in enumerate(par.a_grid): 121 | for i_k,capital in enumerate(par.k_grid): 122 | idx = (t,i_m,i_a,i_k) 123 | 124 | # ii. find optimal consumption and hours at this level of wealth in this period t. 125 | 126 | if t==par.T-1: # last period 127 | obj = lambda x: self.obj_last(x[0],assets,capital,m_type) 128 | 129 | # call optimizer 130 | hours_min = np.fmax( - assets / self.wage_func(capital,t) + 1.0e-5 , 0.0) # minimum amount of hours that ensures positive consumption 131 | init_h = np.maximum(hours_min,2.0) if i_a==0 else np.array([sol.h[t,i_m,i_a-1,i_k]]) 132 | res = minimize(obj,init_h,bounds=((hours_min,np.inf),),method='L-BFGS-B') 133 | 134 | # store results 135 | sol.c[idx] = self.cons_last(res.x[0],assets,capital) 136 | sol.h[idx] = res.x[0] 137 | sol.V[idx] = -res.fun 138 | 139 | else: 140 | 141 | # objective function: negative since we minimize 142 | obj = lambda x: - self.value_of_choice(x[0],x[1],assets,capital,m_type,t) 143 | 144 | # bounds on consumption 145 | lb_c = 0.000001 # avoid dividing with zero 146 | ub_c = np.inf 147 | 148 | # bounds on hours 149 | lb_h = 0.0 150 | ub_h = np.inf 151 | 152 | bounds = ((lb_c,ub_c),(lb_h,ub_h)) 153 | 154 | # call optimizer 155 | init = np.array([lb_c,1.0]) if ((i_a==0) & (i_k==0)) else res.x # initial guess on optimal consumption and hours: last found optimal values 156 | res = minimize(obj,init,bounds=bounds,method='L-BFGS-B',tol=1.0e-10) 157 | 158 | # store results 159 | sol.c[idx] = res.x[0] 160 | sol.h[idx] = res.x[1] 161 | sol.V[idx] = -res.fun 162 | 163 | # last period 164 | def cons_last(self,hours,assets,capital): 165 | par = self.par 166 | 167 | income = self.wage_func(capital,par.T-1) * hours 168 | cons = assets + income 169 | return cons 170 | 171 | def obj_last(self,hours,assets,capital,m_type): 172 | cons = self.cons_last(hours,assets,capital) 173 | return - self.util(cons,hours,m_type) # negative since we minimize 174 | 175 | # earlier periods 176 | def value_of_choice(self,cons,hours,assets,capital,m_type,t): 177 | 178 | # a. unpack 179 | par = self.par 180 | sol = self.sol 181 | 182 | # b. penalty for violating bounds. 183 | penalty = 0.0 184 | if cons < 0.0: 185 | penalty += cons*1_000.0 186 | cons = 1.0e-5 187 | if hours < 0.0: 188 | penalty += hours*1_000.0 189 | hours = 0.0 190 | 191 | # c. utility from consumption 192 | util = self.util(cons,hours,m_type) 193 | 194 | # d. continuation value from savings 195 | V_next = sol.V[t+1,m_type-1] # m_type-1 is the index associated with each type as m_type is (1,2) and index starts at zero in Python. 196 | income = self.wage_func(capital,t) * hours 197 | a_next = (1.0+par.r)*(assets + income - cons) 198 | 199 | # calculate expected value of next period 200 | k_next_no_shock = capital + hours 201 | V_next_interp_no_shock = interp_2d(par.a_grid,par.k_grid, V_next, a_next,k_next_no_shock) 202 | 203 | if par.p>0.0: 204 | k_next_shock = 0.0 # human capital is destroyed 205 | V_next_interp_shock = interp_2d(par.a_grid,par.k_grid,V_next,a_next,k_next_shock) 206 | # similar (but potentially faster): 207 | # V_next_interp_shock = interp_1d(par.a_grid, sol.V[t+1,m_type-1,:,0], a_next) 208 | 209 | EV_next = par.p*V_next_interp_shock + (1.0-par.p)*V_next_interp_no_shock 210 | 211 | else: 212 | EV_next = V_next_interp_no_shock 213 | 214 | # e. return value of choice (including penalty) 215 | return util + par.rho*EV_next + penalty 216 | 217 | 218 | def util(self,c,hours,m_type): 219 | par = self.par 220 | 221 | beta = par.beta 222 | if m_type==2: 223 | beta = par.beta_2 224 | 225 | return (c)**(1.0+par.eta) / (1.0+par.eta) - beta*(hours)**(1.0+par.gamma) / (1.0+par.gamma) 226 | 227 | def wage_func(self,capital,t): 228 | # after tax wage rate 229 | par = self.par 230 | 231 | return (1.0 - par.tau )* par.w_vec[t] * (1.0 + par.alpha * capital) 232 | 233 | ############## 234 | # Simulation # 235 | def simulate(self): 236 | 237 | # a. unpack 238 | par = self.par 239 | sol = self.sol 240 | sim = self.sim 241 | 242 | # b. loop over individuals and time 243 | for i in range(par.simN): 244 | 245 | # i. initialize states 246 | sim.a[i,0] = sim.a_init[i] 247 | sim.k[i,0] = sim.k_init[i] 248 | 249 | for t in range(par.simT): 250 | 251 | # ii. interpolate optimal consumption and hours 252 | idx_sol = (t,sim.m[i]-1) # m_type-1 is the index associated with each type as m_type is (1,2) and index starts at zero in Python. 253 | sim.c[i,t] = interp_2d(par.a_grid,par.k_grid,sol.c[idx_sol],sim.a[i,t],sim.k[i,t]) 254 | sim.h[i,t] = interp_2d(par.a_grid,par.k_grid,sol.h[idx_sol],sim.a[i,t],sim.k[i,t]) 255 | 256 | # iii. store next-period states 257 | if t=(par.Nn-1)): 212 | # cannot have more children 213 | V_next_birth = V_next_no_birth 214 | 215 | else: 216 | kids_next = kids + 1 217 | V_next = sol.V[t+1,kids_next] 218 | V_next_birth = interp_2d(par.a_grid,par.k_grid,V_next,a_next,k_next) 219 | 220 | EV_next = par.p_birth * V_next_birth + (1-par.p_birth)*V_next_no_birth 221 | 222 | # e. return value of choice (including penalty) 223 | return util + par.rho*EV_next + penalty 224 | 225 | 226 | def util(self,c,hours,kids): 227 | par = self.par 228 | 229 | beta = par.beta_0 + par.beta_1*kids 230 | 231 | return (c)**(1.0+par.eta) / (1.0+par.eta) - beta*(hours)**(1.0+par.gamma) / (1.0+par.gamma) 232 | 233 | def wage_func(self,capital,t): 234 | # after tax wage rate 235 | par = self.par 236 | 237 | return (1.0 - par.tau )* par.w_vec[t] * (1.0 + par.alpha * capital) 238 | 239 | ############## 240 | # Simulation # 241 | def simulate(self): 242 | 243 | # a. unpack 244 | par = self.par 245 | sol = self.sol 246 | sim = self.sim 247 | 248 | # b. loop over individuals and time 249 | for i in range(par.simN): 250 | 251 | # i. initialize states 252 | sim.n[i,0] = sim.n_init[i] 253 | sim.a[i,0] = sim.a_init[i] 254 | sim.k[i,0] = sim.k_init[i] 255 | 256 | for t in range(par.simT): 257 | 258 | # ii. interpolate optimal consumption and hours 259 | idx_sol = (t,sim.n[i,t]) 260 | sim.c[i,t] = interp_2d(par.a_grid,par.k_grid,sol.c[idx_sol],sim.a[i,t],sim.k[i,t]) 261 | sim.h[i,t] = interp_2d(par.a_grid,par.k_grid,sol.h[idx_sol],sim.a[i,t],sim.k[i,t]) 262 | 263 | # iii. store next-period states 264 | if t0: 122 | init_h[0] = sol.h1[t,i_k1-1,i_k2] 123 | if i_k2>0: 124 | init_h[1] = sol.h2[t,i_k1,i_k2-1] 125 | 126 | res = minimize(obj,init_h,bounds=bounds) 127 | 128 | # store results 129 | sol.h1[idx] = res.x[0] 130 | sol.h2[idx] = res.x[1] 131 | sol.V[idx] = -res.fun 132 | 133 | 134 | def value_of_choice(self,hours1,hours2,capital1,capital2,V_next): 135 | 136 | # a. unpack 137 | par = self.par 138 | 139 | # b. current utility 140 | util = self.util(hours1,hours2,capital1,capital2) 141 | 142 | # c. continuation value 143 | k1_next = (1.0-par.delta)*capital1 + hours1 144 | k2_next = (1.0-par.delta)*capital2 + hours2 145 | V_next = interp_2d(par.k_grid,par.k_grid,V_next,k1_next,k2_next) 146 | 147 | # d. return value of choice 148 | return util + par.beta*V_next 149 | 150 | 151 | # relevant functions 152 | def consumption(self,hours1,hours2,capital1,capital2): 153 | par = self.par 154 | 155 | income1 = self.wage_func(capital1,1) * hours1 156 | income2 = self.wage_func(capital2,2) * hours2 157 | income_hh = income1+income2 158 | 159 | if par.joint_tax: 160 | tax_hh = self.tax_func(income_hh) 161 | else: 162 | tax_hh = self.tax_func(income1) + self.tax_func(income2) 163 | 164 | return income_hh - tax_hh 165 | 166 | def wage_func(self,capital,sex): 167 | # before tax wage rate 168 | par = self.par 169 | 170 | constant = par.wage_const_1 171 | return_K = par.wage_K_1 172 | if sex>1: 173 | constant = par.wage_const_2 174 | return_K = par.wage_K_2 175 | 176 | return np.exp(constant + return_K * capital) 177 | 178 | def tax_func(self,income): 179 | par = self.par 180 | 181 | rate = 1.0 - par.tax_scale*(income**(-par.tax_pow)) 182 | return rate*income 183 | 184 | def util(self,hours1,hours2,capital1,capital2): 185 | par = self.par 186 | 187 | cons = self.consumption(hours1,hours2,capital1,capital2) 188 | 189 | util_cons = 2*(cons/2)**(1.0+par.eta) / (1.0+par.eta) 190 | util_hours1 = par.rho_1*(hours1)**(1.0+par.gamma) / (1.0+par.gamma) 191 | util_hours2 = par.rho_2*(hours2)**(1.0+par.gamma) / (1.0+par.gamma) 192 | 193 | return util_cons - util_hours1 - util_hours2 194 | 195 | ############## 196 | # Simulation # 197 | def simulate(self): 198 | 199 | # a. unpack 200 | par = self.par 201 | sol = self.sol 202 | sim = self.sim 203 | 204 | # b. loop over individuals and time 205 | for i in range(par.simN): 206 | 207 | # i. initialize states 208 | sim.k1[i,0] = sim.k1_init[i] 209 | sim.k2[i,0] = sim.k2_init[i] 210 | 211 | for t in range(par.simT): 212 | 213 | # ii. interpolate optimal hours 214 | idx_sol = t 215 | sim.h1[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h1[idx_sol],sim.k1[i,t],sim.k2[i,t]) 216 | sim.h2[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h2[idx_sol],sim.k1[i,t],sim.k2[i,t]) 217 | 218 | # store income 219 | sim.income1[i,t] = self.wage_func(sim.k1[i,t],1)*sim.h1[i,t] 220 | sim.income2[i,t] = self.wage_func(sim.k2[i,t],2)*sim.h2[i,t] 221 | 222 | # iii. store next-period states 223 | if t\n", 17 | "**Motivated** by the study “Child-Related Transfers, Household Labor Supply and Welfare” by [Guner et al. (2020)](https://academic.oup.com/restud/article/87/5/2290/5809564).
\n", 18 | "**Goal** is to replicate effects of child-related transfers.\n", 19 | "\n", 20 | "For simplicity, couples cannot divorce nor save.\n", 21 | "\n", 22 | "The **Bellman equation** and the recursive formulation of our simple model is \n", 23 | "$$\n", 24 | "\\begin{align*}\n", 25 | "V_{t}(n_{t},K_{1,t},K_{2,t}) & =\\max_{h_{1,t},h_{2,t}}U(c_{t},h_{1,t},h_{2,t},n_{t})\\\\\n", 26 | " & \\qquad\\qquad+\\beta\\mathbb{E}_{t}[V_{t+1}(n_{t+1},K_{1,t+1},K_{2,t+1})]\\\\\n", 27 | "c_{t} & =\\sum_{j=1}^{2}w_{j,t}h_{j,t}-T(w_{1,t}h_{1,t},w_{2,t}h_{2,t})\\\\\n", 28 | " & \\qquad+\\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t})\\\\\n", 29 | "n_{t+1} & =\\begin{cases}\n", 30 | "\\begin{array}{ll}\n", 31 | "1 & \\text{with prob. } p(n_{t})\\\\\n", 32 | "0 & \\text{with prob. } 1-p(n_{t})\n", 33 | "\\end{array}\\end{cases}\\\\\n", 34 | "\\log w_{j,t} & =\\alpha_{j,0}+\\alpha_{j,1}K_{j,t},\\;j\\in\\{1,2\\}\\\\\n", 35 | "K_{j,t+1} & =(1-\\delta)K_{j,t}+h_{j,t},\\;j\\in\\{1,2\\}\n", 36 | "\\end{align*}\n", 37 | "$$\n", 38 | "\n", 39 | "**Child-related transfers:** We assume that if both work, they have to buy childcare.
\n", 40 | "This means that conditional transfers are always a subsidy (cannot do 5 or 6 in taxonomy of Guner et al. (2020)).
\n", 41 | "Child-related transfers are\n", 42 | "$$\n", 43 | "\\begin{align*}\n", 44 | "\\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t}) & =\\mathcal{C}_{1}(n_{t})+\\mathcal{C}_{2}(n_{t},Y_{t})\\\\\n", 45 | " & +[\\mathcal{C}_{3}(n_{t})+\\mathcal{C}_{4}(n_{t},Y_{t})]\\cdot\\mathbf{1}(h_{1,t}\\cdot h_{2,t}>0)\n", 46 | "\\end{align*}\n", 47 | "$$\n", 48 | "where household income is\n", 49 | "$$\n", 50 | "Y_{t}=\\sum_{j=1}^{2}w_{j,t}h_{j,t}.\n", 51 | "$$\n", 52 | "\n", 53 | "**Preferences** are sum of individuals\n", 54 | "$$\n", 55 | "U(c_{t},h_{1,t},h_{2,t})=2\\frac{(c_{t}/2)^{1+\\eta}}{1+\\eta}-\\rho_{1}(n_{t})\\frac{h_{1,t}^{1+\\gamma}}{1+\\gamma}-\\rho_{2}(n_{t})\\frac{h_{2,t}^{1+\\gamma}}{1+\\gamma}\n", 56 | "$$\n", 57 | "with \n", 58 | "$$\n", 59 | "\\rho_{j}(n_{t})=\\rho_{0,j}+\\rho_{1,j}n_{t}\n", 60 | "$$\n", 61 | "\n", 62 | "**Taxes** are on the household level\n", 63 | "$$\n", 64 | "T(Y_{1},Y_{2})=(1-\\lambda(Y_{1}+Y_{2})^{-\\tau})\\cdot(Y_{1}+Y_{2})\n", 65 | "$$\n", 66 | "\n", 67 | "**Expected value** is\n", 68 | "$$\n", 69 | "\\begin{align*}\n", 70 | "\\mathbb{E}_{t}[V_{t+1}(n_{t+1},a_{t+1},k_{t+1})] & =p(n_{t})V_{t+1}(n_{t}+1,a_{t+1},k_{t+1})\\\\\n", 71 | " & +(1-p(n_{t}))V_{t+1}(n_{t},a_{t+1},k_{t+1})\n", 72 | "\\end{align*}\n", 73 | "$$\n", 74 | "\n", 75 | "**Terminal period:** There are no bequests such that\n", 76 | "$$\n", 77 | "V_{T}(n_T,K_{1,T},K_{2,T}) =\\max_{h_{1,T},h_{2,T}}U(c_{T},h_{1,T},h_{2,T})\n", 78 | "$$" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "## Setup" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 180, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "The autoreload extension is already loaded. To reload it, use:\n", 98 | " %reload_ext autoreload\n" 99 | ] 100 | } 101 | ], 102 | "source": [ 103 | "%load_ext autoreload\n", 104 | "%autoreload 2\n", 105 | "\n", 106 | "import numpy as np\n", 107 | "import numba as nb\n", 108 | "\n", 109 | "import matplotlib.pyplot as plt\n", 110 | "from mpl_toolkits import mplot3d" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "# Consumption-Saving Model" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "# load local model file and initialize model class\n", 127 | "# -> copy .py-module from last time and modify it!" 128 | ] 129 | }, 130 | { 131 | "attachments": {}, 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "# Simulate child-related transfers reforms" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "# modify code and run it..." 145 | ] 146 | } 147 | ], 148 | "metadata": { 149 | "kernelspec": { 150 | "display_name": "Python 3", 151 | "language": "python", 152 | "name": "python3" 153 | }, 154 | "language_info": { 155 | "codemirror_mode": { 156 | "name": "ipython", 157 | "version": 3 158 | }, 159 | "file_extension": ".py", 160 | "mimetype": "text/x-python", 161 | "name": "python", 162 | "nbconvert_exporter": "python", 163 | "pygments_lexer": "ipython3", 164 | "version": "3.8.10 (default, May 19 2021, 13:12:57) [MSC v.1916 64 bit (AMD64)]" 165 | }, 166 | "toc-autonumbering": true, 167 | "vscode": { 168 | "interpreter": { 169 | "hash": "2a1ca330d9582a7d9f549c991d1ebe88efa30325a2a9c927421566fc2176e6bd" 170 | } 171 | } 172 | }, 173 | "nbformat": 4, 174 | "nbformat_minor": 4 175 | } 176 | -------------------------------------------------------------------------------- /2023/07/7_household_labor_transfers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/07/7_household_labor_transfers.pdf -------------------------------------------------------------------------------- /2023/08/8_household_models.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/08/8_household_models.pdf -------------------------------------------------------------------------------- /2023/08/UserFunctions.py: -------------------------------------------------------------------------------- 1 | # set gender indication as globals 2 | woman = 1 3 | man = 2 4 | 5 | ############################ 6 | # User-specified functions # 7 | ############################ 8 | def util(c_priv,c_pub,gender,par,love=0.0): 9 | if gender == woman: 10 | rho = par.rho_w 11 | phi = par.phi_w 12 | alpha1 = par.alpha1_w 13 | alpha2 = par.alpha2_w 14 | else: 15 | rho = par.rho_m 16 | phi = par.phi_m 17 | alpha1 = par.alpha1_m 18 | alpha2 = par.alpha2_m 19 | 20 | return ((alpha1*c_priv**phi + alpha2*c_pub**phi)**(1.0-rho))/(1.0-rho) + love 21 | 22 | def resources_couple(A,par): 23 | return par.R*A + par.inc_w + par.inc_m 24 | 25 | def resources_single(A,gender,par): 26 | income = par.inc_w 27 | if gender == man: 28 | income = par.inc_m 29 | 30 | return par.R*A + income 31 | 32 | def cons_priv_single(C_tot,gender,par): 33 | # closed form solution for intra-period problem of single. 34 | if gender == woman: 35 | rho = par.rho_w 36 | phi = par.phi_w 37 | alpha1 = par.alpha1_w 38 | alpha2 = par.alpha2_w 39 | else: 40 | rho = par.rho_m 41 | phi = par.phi_m 42 | alpha1 = par.alpha1_m 43 | alpha2 = par.alpha2_m 44 | 45 | return C_tot/(1.0 + (alpha2/alpha1)**(1.0/(1.0-phi)) ) 46 | -------------------------------------------------------------------------------- /2023/08/__pycache__/Bargaining.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/08/__pycache__/Bargaining.cpython-38.pyc -------------------------------------------------------------------------------- /2023/08/__pycache__/UserFunctions.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/08/__pycache__/UserFunctions.cpython-38.pyc -------------------------------------------------------------------------------- /2023/09/9_divorce_law.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/09/9_divorce_law.pdf -------------------------------------------------------------------------------- /2023/09/UserFunctions.py: -------------------------------------------------------------------------------- 1 | # set gender indication as globals 2 | woman = 1 3 | man = 2 4 | 5 | ############################ 6 | # User-specified functions # 7 | ############################ 8 | def util(c_priv,c_pub,gender,par,love=0.0): 9 | if gender == woman: 10 | rho = par.rho_w 11 | phi = par.phi_w 12 | alpha1 = par.alpha1_w 13 | alpha2 = par.alpha2_w 14 | else: 15 | rho = par.rho_m 16 | phi = par.phi_m 17 | alpha1 = par.alpha1_m 18 | alpha2 = par.alpha2_m 19 | 20 | return ((alpha1*c_priv**phi + alpha2*c_pub**phi)**(1.0-rho))/(1.0-rho) + love 21 | 22 | def resources_couple(A,par): 23 | return par.R*A + par.inc_w + par.inc_m 24 | 25 | def resources_single(A,gender,par): 26 | income = par.inc_w 27 | if gender == man: 28 | income = par.inc_m 29 | 30 | return par.R*A + income 31 | 32 | def cons_priv_single(C_tot,gender,par): 33 | # closed form solution for intra-period problem of single. 34 | if gender == woman: 35 | rho = par.rho_w 36 | phi = par.phi_w 37 | alpha1 = par.alpha1_w 38 | alpha2 = par.alpha2_w 39 | else: 40 | rho = par.rho_m 41 | phi = par.phi_m 42 | alpha1 = par.alpha1_m 43 | alpha2 = par.alpha2_m 44 | 45 | return C_tot/(1.0 + (alpha2/alpha1)**(1.0/(1.0-phi)) ) 46 | -------------------------------------------------------------------------------- /2023/09/__pycache__/Bargaining.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/09/__pycache__/Bargaining.cpython-38.pyc -------------------------------------------------------------------------------- /2023/09/__pycache__/UserFunctions.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/09/__pycache__/UserFunctions.cpython-38.pyc -------------------------------------------------------------------------------- /2023/10/10_marriage_divorce.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/10/10_marriage_divorce.pdf -------------------------------------------------------------------------------- /2023/11/11_FertilityFamilyLabor.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/11/11_FertilityFamilyLabor.pdf -------------------------------------------------------------------------------- /2023/12/12_HamishLow_CEBI_PhD_Lecture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/12/12_HamishLow_CEBI_PhD_Lecture.pdf -------------------------------------------------------------------------------- /2023/13/13_time_allocation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/13/13_time_allocation.pdf -------------------------------------------------------------------------------- /2023/13/CES timeuse.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/13/CES timeuse.pdf -------------------------------------------------------------------------------- /2023/Assignments/01/Assignment 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/Assignments/01/Assignment 1.pdf -------------------------------------------------------------------------------- /2023/Assignments/02/Assignment 2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/Assignments/02/Assignment 2.pdf -------------------------------------------------------------------------------- /2023/Assignments/03/Assignment 3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/Assignments/03/Assignment 3.pdf -------------------------------------------------------------------------------- /2023/Exam/DynamicSpecialization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Household Behavior over the Life Cycle. Exam 2023.\n", 9 | "## Setup" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "%load_ext autoreload\n", 19 | "%autoreload 2\n", 20 | "\n", 21 | "import numpy as np\n", 22 | "from scipy.optimize import minimize\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "\n", 25 | "from DynamicSpecializationModel import DynamicSpecializationModelClass" 26 | ] 27 | }, 28 | { 29 | "attachments": {}, 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Load model and solve it" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "Wall time: 1.66 s\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "model = DynamicSpecializationModelClass()\n", 51 | "%time model.solve()" 52 | ] 53 | }, 54 | { 55 | "attachments": {}, 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Simulate" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 3, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "name": "stdout", 69 | "output_type": "stream", 70 | "text": [ 71 | "Wall time: 1.11 s\n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "%time model.simulate()" 77 | ] 78 | } 79 | ], 80 | "metadata": { 81 | "kernelspec": { 82 | "display_name": "Python 3", 83 | "language": "python", 84 | "name": "python3" 85 | }, 86 | "language_info": { 87 | "codemirror_mode": { 88 | "name": "ipython", 89 | "version": 3 90 | }, 91 | "file_extension": ".py", 92 | "mimetype": "text/x-python", 93 | "name": "python", 94 | "nbconvert_exporter": "python", 95 | "pygments_lexer": "ipython3", 96 | "version": "3.8.10" 97 | }, 98 | "orig_nbformat": 4 99 | }, 100 | "nbformat": 4, 101 | "nbformat_minor": 2 102 | } 103 | -------------------------------------------------------------------------------- /2023/Exam/HouseholdBehavior_exam.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2023/Exam/HouseholdBehavior_exam.pdf -------------------------------------------------------------------------------- /2024/01/1_Introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/01/1_Introduction.pdf -------------------------------------------------------------------------------- /2024/01/ConSavModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | 4 | from EconModel import EconModelClass, jit 5 | 6 | from consav.grids import nonlinspace 7 | from consav.linear_interp import interp_1d 8 | 9 | class ConSavModelClass(EconModelClass): 10 | 11 | def settings(self): 12 | """ fundamental settings """ 13 | 14 | pass 15 | 16 | def setup(self): 17 | """ set baseline parameters """ 18 | 19 | # unpack 20 | par = self.par 21 | 22 | par.T = 20 # time periods 23 | 24 | # preferences 25 | par.beta = 0.98 # discount factor 26 | par.rho = 2.0 # CRRA coefficient 27 | 28 | # income 29 | par.y = 1.0 # income level 30 | 31 | # saving 32 | par.r = 0.02 # interest rate 33 | 34 | # grid 35 | par.a_max = 30.0 # maximum point in wealth grid 36 | par.Na = 200 # number of grid points in wealth grid 37 | 38 | # simulation 39 | par.simT = par.T # number of periods 40 | par.simN = 1_000 # number of individuals 41 | 42 | 43 | def allocate(self): 44 | """ allocate model """ 45 | 46 | # unpack 47 | par = self.par 48 | sol = self.sol 49 | sim = self.sim 50 | 51 | # a. asset grid 52 | par.a_grid = nonlinspace(0.0,par.a_max,par.Na,1.1) 53 | 54 | # b. income 55 | par.yt = par.y * np.ones(par.T) 56 | 57 | # c. solution arrays 58 | shape = (par.T,par.Na) 59 | sol.c = np.nan + np.zeros(shape) 60 | sol.V = np.nan + np.zeros(shape) 61 | 62 | # d. simulation arrays 63 | shape = (par.simN,par.simT) 64 | sim.c = np.nan + np.zeros(shape) 65 | sim.a = np.nan + np.zeros(shape) 66 | 67 | # e. initialization 68 | sim.a_init = np.zeros(par.simN) 69 | 70 | 71 | ############ 72 | # Solution # 73 | def solve(self): 74 | 75 | # a. unpack 76 | par = self.par 77 | sol = self.sol 78 | 79 | # b. solve last period 80 | t = par.T-1 81 | sol.c[t,:] = par.a_grid + par.yt[t] 82 | sol.V[t,:] = self.util(sol.c[t,:]) 83 | 84 | # c. loop backwards [note, the last element, N, in range(N) is not included in the loop due to index starting at 0] 85 | for t in reversed(range(par.T-1)): 86 | 87 | # i. loop over state variable: wealth in beginning of period 88 | for ia,assets in enumerate(par.a_grid): 89 | 90 | # ii. find optimal consumption at this level of wealth in this period t. 91 | 92 | # objective function: negative since we minimize 93 | obj = lambda c: - self.value_of_choice(c[0],assets,t) 94 | 95 | # bounds on consumption 96 | lb = 0.000001 # avoid dividing with zero 97 | ub = assets + par.yt[t] 98 | 99 | # call optimizer 100 | if lb>=ub: # if the bounds are not feasible, set consumption to all resources 101 | sol.c[t,ia] = ub 102 | sol.V[t,ia] = -obj(sol.c[t,ia]) 103 | 104 | else: 105 | c_init = np.array(lb+0.5*ub) # initial guess on optimal consumption 106 | res = minimize(obj,c_init,bounds=((lb,ub),),method='SLSQP') 107 | 108 | # store results 109 | sol.c[t,ia] = res.x[0] 110 | sol.V[t,ia] = -res.fun 111 | 112 | 113 | def value_of_choice(self,cons,assets,t): 114 | 115 | # a. unpack 116 | par = self.par 117 | sol = self.sol 118 | 119 | # b. utility from consumption 120 | util = self.util(cons) 121 | 122 | # c. continuation value from savings 123 | V_next = sol.V[t+1] 124 | a_next = (1.0+par.r)*(assets + par.yt[t] - cons) 125 | V_next_interp = interp_1d(par.a_grid,V_next,a_next) 126 | 127 | # d. return value of choice 128 | return util + par.beta*V_next_interp 129 | 130 | 131 | def util(self,c): 132 | par = self.par 133 | 134 | return (c)**(1.0-par.rho) / (1.0-par.rho) 135 | 136 | 137 | ############## 138 | # Simulation # 139 | def simulate(self): 140 | 141 | # a. unpack 142 | par = self.par 143 | sol = self.sol 144 | sim = self.sim 145 | 146 | # b. loop over individuals and time 147 | for i in range(par.simN): 148 | 149 | # i. initialize assets 150 | sim.a[i,0] = sim.a_init[i] 151 | 152 | for t in range(par.simT): 153 | if t=(par.Nn-1)): 209 | # cannot have more children 210 | V_next_birth = V_next_no_birth 211 | 212 | else: 213 | kids_next = kids + 1 214 | V_next = sol.V[t+1,kids_next] 215 | V_next_birth = interp_2d(par.a_grid,par.k_grid,V_next,a_next,k_next) 216 | 217 | EV_next = par.p_birth * V_next_birth + (1-par.p_birth)*V_next_no_birth 218 | 219 | # e. return value of choice (including penalty) 220 | return util + par.rho*EV_next + penalty 221 | 222 | 223 | def util(self,c,hours,kids): 224 | par = self.par 225 | 226 | beta = par.beta_0 + par.beta_1*kids 227 | 228 | return (c)**(1.0+par.eta) / (1.0+par.eta) - beta*(hours)**(1.0+par.gamma) / (1.0+par.gamma) 229 | 230 | def wage_func(self,capital,t): 231 | # after tax wage rate 232 | par = self.par 233 | 234 | return (1.0 - par.tau )* par.w_vec[t] * (1.0 + par.alpha * capital) 235 | 236 | ############## 237 | # Simulation # 238 | def simulate(self): 239 | 240 | # a. unpack 241 | par = self.par 242 | sol = self.sol 243 | sim = self.sim 244 | 245 | # b. loop over individuals and time 246 | for i in range(par.simN): 247 | 248 | # i. initialize states 249 | sim.n[i,0] = sim.n_init[i] 250 | sim.a[i,0] = sim.a_init[i] 251 | sim.k[i,0] = sim.k_init[i] 252 | 253 | for t in range(par.simT): 254 | 255 | # ii. interpolate optimal consumption and hours 256 | idx_sol = (t,sim.n[i,t]) 257 | sim.c[i,t] = interp_2d(par.a_grid,par.k_grid,sol.c[idx_sol],sim.a[i,t],sim.k[i,t]) 258 | sim.h[i,t] = interp_2d(par.a_grid,par.k_grid,sol.h[idx_sol],sim.a[i,t],sim.k[i,t]) 259 | 260 | # iii. store next-period states 261 | if t0: 122 | init_h[0] = sol.h1[t,i_k1-1,i_k2] 123 | if i_k2>0: 124 | init_h[1] = sol.h2[t,i_k1,i_k2-1] 125 | 126 | res = minimize(obj,init_h,bounds=bounds) 127 | 128 | # store results 129 | sol.h1[idx] = res.x[0] 130 | sol.h2[idx] = res.x[1] 131 | sol.V[idx] = -res.fun 132 | 133 | 134 | def value_of_choice(self,hours1,hours2,capital1,capital2,V_next): 135 | 136 | # a. unpack 137 | par = self.par 138 | 139 | # b. current utility 140 | util = self.util(hours1,hours2,capital1,capital2) 141 | 142 | # c. continuation value 143 | k1_next = (1.0-par.delta)*capital1 + hours1 144 | k2_next = (1.0-par.delta)*capital2 + hours2 145 | V_next = interp_2d(par.k_grid,par.k_grid,V_next,k1_next,k2_next) 146 | 147 | # d. return value of choice 148 | return util + par.beta*V_next 149 | 150 | 151 | # relevant functions 152 | def consumption(self,hours1,hours2,capital1,capital2): 153 | par = self.par 154 | 155 | income1 = self.wage_func(capital1,1) * hours1 156 | income2 = self.wage_func(capital2,2) * hours2 157 | income_hh = income1+income2 158 | 159 | if par.joint_tax: 160 | tax_hh = self.tax_func(income_hh) 161 | else: 162 | tax_hh = self.tax_func(income1) + self.tax_func(income2) 163 | 164 | return income_hh - tax_hh 165 | 166 | def wage_func(self,capital,sex): 167 | # before tax wage rate 168 | par = self.par 169 | 170 | constant = par.wage_const_1 171 | return_K = par.wage_K_1 172 | if sex>1: 173 | constant = par.wage_const_2 174 | return_K = par.wage_K_2 175 | 176 | return np.exp(constant + return_K * capital) 177 | 178 | def tax_func(self,income): 179 | par = self.par 180 | 181 | rate = 1.0 - par.tax_scale*(income**(-par.tax_pow)) 182 | return rate*income 183 | 184 | def util(self,hours1,hours2,capital1,capital2): 185 | par = self.par 186 | 187 | cons = self.consumption(hours1,hours2,capital1,capital2) 188 | 189 | util_cons = 2*(cons/2)**(1.0+par.eta) / (1.0+par.eta) 190 | util_hours1 = par.rho_1*(hours1)**(1.0+par.gamma) / (1.0+par.gamma) 191 | util_hours2 = par.rho_2*(hours2)**(1.0+par.gamma) / (1.0+par.gamma) 192 | 193 | return util_cons - util_hours1 - util_hours2 194 | 195 | ############## 196 | # Simulation # 197 | def simulate(self): 198 | 199 | # a. unpack 200 | par = self.par 201 | sol = self.sol 202 | sim = self.sim 203 | 204 | # b. loop over individuals and time 205 | for i in range(par.simN): 206 | 207 | # i. initialize states 208 | sim.k1[i,0] = sim.k1_init[i] 209 | sim.k2[i,0] = sim.k2_init[i] 210 | 211 | for t in range(par.simT): 212 | 213 | # ii. interpolate optimal hours 214 | idx_sol = t 215 | sim.h1[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h1[idx_sol],sim.k1[i,t],sim.k2[i,t]) 216 | sim.h2[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h2[idx_sol],sim.k1[i,t],sim.k2[i,t]) 217 | 218 | # store income 219 | sim.income1[i,t] = self.wage_func(sim.k1[i,t],1)*sim.h1[i,t] 220 | sim.income2[i,t] = self.wage_func(sim.k2[i,t],2)*sim.h2[i,t] 221 | 222 | # iii. store next-period states 223 | if t\n", 17 | "**Motivated** by the study “Child-Related Transfers, Household Labor Supply and Welfare” by [Guner et al. (2020)](https://academic.oup.com/restud/article/87/5/2290/5809564).
\n", 18 | "**Goal** is to replicate effects of child-related transfers.\n", 19 | "\n", 20 | "For simplicity, couples cannot divorce nor save.\n", 21 | "\n", 22 | "The **Bellman equation** and the recursive formulation of our simple model is \n", 23 | "$$\n", 24 | "\\begin{align*}\n", 25 | "V_{t}(n_{t},K_{1,t},K_{2,t}) & =\\max_{h_{1,t},h_{2,t}}U(c_{t},h_{1,t},h_{2,t},n_{t})\\\\\n", 26 | " & \\qquad\\qquad+\\beta\\mathbb{E}_{t}[V_{t+1}(n_{t+1},K_{1,t+1},K_{2,t+1})]\\\\\n", 27 | "c_{t} & =\\sum_{j=1}^{2}w_{j,t}h_{j,t}-T(w_{1,t}h_{1,t},w_{2,t}h_{2,t})\\\\\n", 28 | " & \\qquad+\\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t})\\\\\n", 29 | "n_{t+1} & =\\begin{cases}\n", 30 | "\\begin{array}{ll}\n", 31 | "n_t + 1 & \\text{with prob. } p(n_{t})\\\\\n", 32 | "n_t & \\text{with prob. } 1-p(n_{t})\n", 33 | "\\end{array}\\end{cases}\\\\\n", 34 | "\\log w_{j,t} & =\\alpha_{j,0}+\\alpha_{j,1}K_{j,t},\\;j\\in\\{1,2\\}\\\\\n", 35 | "K_{j,t+1} & =(1-\\delta)K_{j,t}+h_{j,t},\\;j\\in\\{1,2\\}\n", 36 | "\\end{align*}\n", 37 | "$$\n", 38 | "where \n", 39 | "$$\n", 40 | "p(n_t) = \\begin{cases}\n", 41 | "\\begin{array}{ll}\n", 42 | "p_n & \\text{if } n_{t}=0\\\\\n", 43 | "0 & \\text{if } n_{t}=1.\n", 44 | "\\end{array}\\end{cases}\n", 45 | "$$\n", 46 | "\n", 47 | "**Child-related transfers:** We assume that if both work, they have to buy childcare.
\n", 48 | "This means that conditional transfers are always a subsidy (cannot do 5 or 6 in taxonomy of Guner et al. (2020)).
\n", 49 | "Child-related transfers are\n", 50 | "$$\n", 51 | "\\begin{align*}\n", 52 | "\\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t}) & =\\mathcal{C}_{1}(n_{t})+\\mathcal{C}_{2}(n_{t},Y_{t})\\\\\n", 53 | " & +[\\mathcal{C}_{3}(n_{t})+\\mathcal{C}_{4}(n_{t},Y_{t})]\\cdot\\mathbf{1}(h_{1,t}\\cdot h_{2,t}>0)\n", 54 | "\\end{align*}\n", 55 | "$$\n", 56 | "where household income is\n", 57 | "$$\n", 58 | "Y_{t}=\\sum_{j=1}^{2}w_{j,t}h_{j,t}.\n", 59 | "$$\n", 60 | "\n", 61 | "The code could look something like\n", 62 | "```python\n", 63 | "def child_tran(self,hours1,hours2,income_hh,kids):\n", 64 | " par = self.par\n", 65 | " if kids<1:\n", 66 | " return 0.0\n", 67 | " \n", 68 | " else:\n", 69 | " C1 = par.uncon_uni #unconditional, universal transfer (>0)\n", 70 | " C2 = np.fmax(par.means_level - par.means_slope*income_hh , 0.0) #means-tested transfer (>0)\n", 71 | " # child-care related (net-of-subsidy costs)\n", 72 | " both_work = (hours1>0) * (hours2>0)\n", 73 | " C3 = par.cond*both_work #all working couples has this net cost (<0)\n", 74 | " C4 = par.cond_high*both_work*(income_hh>0.5) #low-income couples do not have this net-cost (<0)\n", 75 | "\n", 76 | " return C1+C2+C3+C4\n", 77 | "```\n", 78 | "\n", 79 | "**Preferences** are sum of individuals\n", 80 | "$$\n", 81 | "U(c_{t},h_{1,t},h_{2,t})=2\\frac{(c_{t}/2)^{1+\\eta}}{1+\\eta}-\\rho_{1}(n_{t})\\frac{h_{1,t}^{1+\\gamma}}{1+\\gamma}-\\rho_{2}(n_{t})\\frac{h_{2,t}^{1+\\gamma}}{1+\\gamma}\n", 82 | "$$\n", 83 | "with \n", 84 | "$$\n", 85 | "\\rho_{j}(n_{t})=\\rho_{0,j}+\\rho_{1,j}n_{t}\n", 86 | "$$\n", 87 | "\n", 88 | "**Taxes** are on the household level and child-related transfers/costs are included in taxable income\n", 89 | "$$\n", 90 | "T(\\tilde{Y})=(1-\\lambda(\\tilde{Y})^{-\\tau})\\cdot(\\tilde{Y})\n", 91 | "$$\n", 92 | "where $\\tilde{Y} = Y + \\mathcal{C}(n_{t},h_{1,t},h_{2,t},w_{1,t},w_{2,t})$.\n", 93 | "\n", 94 | "**Expected value** is\n", 95 | "$$\n", 96 | "\\begin{align*}\n", 97 | "\\mathbb{E}_{t}[V_{t+1}(n_{t+1},K_{1,t+1},K_{2,t+1})] & =p(n_{t})V_{t+1}(n_{t}+1,K_{1,t+1},K_{2,t+1})\\\\\n", 98 | " & +(1-p(n_{t}))V_{t+1}(n_{t},K_{1,t+1},K_{2,t+1})\n", 99 | "\\end{align*}\n", 100 | "$$\n", 101 | "\n", 102 | "**Terminal period:** There are no bequests such that\n", 103 | "$$\n", 104 | "V_{T}(n_T,K_{1,T},K_{2,T}) =\\max_{h_{1,T},h_{2,T}}U(c_{T},h_{1,T},h_{2,T})\n", 105 | "$$" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Setup" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 180, 118 | "metadata": {}, 119 | "outputs": [ 120 | { 121 | "name": "stdout", 122 | "output_type": "stream", 123 | "text": [ 124 | "The autoreload extension is already loaded. To reload it, use:\n", 125 | " %reload_ext autoreload\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "%load_ext autoreload\n", 131 | "%autoreload 2\n", 132 | "\n", 133 | "import numpy as np\n", 134 | "import numba as nb\n", 135 | "\n", 136 | "import matplotlib.pyplot as plt\n", 137 | "from mpl_toolkits import mplot3d" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "# Dual-Earner Model with Children" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "# load local model file and initialize model class\n", 154 | "# -> copy .py-module from last time and modify it!" 155 | ] 156 | }, 157 | { 158 | "attachments": {}, 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "# Simulate child-related transfers reforms" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "# modify code and run it..." 172 | ] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "Python 3", 178 | "language": "python", 179 | "name": "python3" 180 | }, 181 | "language_info": { 182 | "codemirror_mode": { 183 | "name": "ipython", 184 | "version": 3 185 | }, 186 | "file_extension": ".py", 187 | "mimetype": "text/x-python", 188 | "name": "python", 189 | "nbconvert_exporter": "python", 190 | "pygments_lexer": "ipython3", 191 | "version": "3.8.10 (default, May 19 2021, 13:12:57) [MSC v.1916 64 bit (AMD64)]" 192 | }, 193 | "toc-autonumbering": true, 194 | "vscode": { 195 | "interpreter": { 196 | "hash": "2a1ca330d9582a7d9f549c991d1ebe88efa30325a2a9c927421566fc2176e6bd" 197 | } 198 | } 199 | }, 200 | "nbformat": 4, 201 | "nbformat_minor": 4 202 | } 203 | -------------------------------------------------------------------------------- /2024/07/7_household_labor_transfers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/07/7_household_labor_transfers.pdf -------------------------------------------------------------------------------- /2024/07_expost/7_household_labor_transfers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/07_expost/7_household_labor_transfers.pdf -------------------------------------------------------------------------------- /2024/08/8_household_models.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/08/8_household_models.pdf -------------------------------------------------------------------------------- /2024/08/UserFunctions.py: -------------------------------------------------------------------------------- 1 | # set gender indication as globals 2 | woman = 1 3 | man = 2 4 | 5 | ############################ 6 | # User-specified functions # 7 | ############################ 8 | def util(c_priv,c_pub,gender,par,love=0.0): 9 | if gender == woman: 10 | rho = par.rho_w 11 | phi = par.phi_w 12 | alpha1 = par.alpha1_w 13 | alpha2 = par.alpha2_w 14 | else: 15 | rho = par.rho_m 16 | phi = par.phi_m 17 | alpha1 = par.alpha1_m 18 | alpha2 = par.alpha2_m 19 | 20 | return ((alpha1*c_priv**phi + alpha2*c_pub**phi)**(1.0-rho))/(1.0-rho) + love 21 | 22 | def resources_couple(A,par): 23 | return par.R*A + par.inc_w + par.inc_m 24 | 25 | def resources_single(A,gender,par): 26 | income = par.inc_w 27 | if gender == man: 28 | income = par.inc_m 29 | 30 | return par.R*A + income 31 | 32 | def cons_priv_single(C_tot,gender,par): 33 | # closed form solution for intra-period problem of single. 34 | if gender == woman: 35 | rho = par.rho_w 36 | phi = par.phi_w 37 | alpha1 = par.alpha1_w 38 | alpha2 = par.alpha2_w 39 | else: 40 | rho = par.rho_m 41 | phi = par.phi_m 42 | alpha1 = par.alpha1_m 43 | alpha2 = par.alpha2_m 44 | 45 | return C_tot/(1.0 + (alpha2/alpha1)**(1.0/(1.0-phi)) ) 46 | -------------------------------------------------------------------------------- /2024/09/9_divorce_law.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/09/9_divorce_law.pdf -------------------------------------------------------------------------------- /2024/09/UserFunctions.py: -------------------------------------------------------------------------------- 1 | # set gender indication as globals 2 | woman = 1 3 | man = 2 4 | 5 | ############################ 6 | # User-specified functions # 7 | ############################ 8 | def util(c_priv,c_pub,gender,par,love=0.0): 9 | if gender == woman: 10 | rho = par.rho_w 11 | phi = par.phi_w 12 | alpha1 = par.alpha1_w 13 | alpha2 = par.alpha2_w 14 | else: 15 | rho = par.rho_m 16 | phi = par.phi_m 17 | alpha1 = par.alpha1_m 18 | alpha2 = par.alpha2_m 19 | 20 | return ((alpha1*c_priv**phi + alpha2*c_pub**phi)**(1.0-rho))/(1.0-rho) + love 21 | 22 | def resources_couple(A,par): 23 | return par.R*A + par.inc_w + par.inc_m 24 | 25 | def resources_single(A,gender,par): 26 | income = par.inc_w 27 | if gender == man: 28 | income = par.inc_m 29 | 30 | return par.R*A + income 31 | 32 | def cons_priv_single(C_tot,gender,par): 33 | # closed form solution for intra-period problem of single. 34 | if gender == woman: 35 | rho = par.rho_w 36 | phi = par.phi_w 37 | alpha1 = par.alpha1_w 38 | alpha2 = par.alpha2_w 39 | else: 40 | rho = par.rho_m 41 | phi = par.phi_m 42 | alpha1 = par.alpha1_m 43 | alpha2 = par.alpha2_m 44 | 45 | return C_tot/(1.0 + (alpha2/alpha1)**(1.0/(1.0-phi)) ) 46 | -------------------------------------------------------------------------------- /2024/10/10_marriage_divorce.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/10/10_marriage_divorce.pdf -------------------------------------------------------------------------------- /2024/11/11_FertilityFamilyLabor.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/11/11_FertilityFamilyLabor.pdf -------------------------------------------------------------------------------- /2024/12/12_HamishLow_CEBI_PhD_Lecture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasHJorgensen/HouseholdBehaviorCourse/8ac82a95657afd0885cd0e5210441abc3cd27475/2024/12/12_HamishLow_CEBI_PhD_Lecture.pdf -------------------------------------------------------------------------------- /2024/12_expost/Assignment2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Life-Cycle Labor Supply of Couples" 9 | ] 10 | }, 11 | { 12 | "attachments": {}, 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "Solves and simulates a $T$-period labor supply model with two-earner couples.
\n", 17 | "**Motivated** by the study “Are Marriage-Related Taxes and Social Security Benefits Holding Back Female Labor Supply?” by Borella et al. (forthcoming).
\n", 18 | "**Goal** is to replicate effects of individual vs. joint taxation.\n", 19 | "\n", 20 | "For simplicity, couples cannot divorce nor save.\n", 21 | "\n", 22 | "The **Bellman equation** and the recursive formulation of our simple model is \n", 23 | "$$\n", 24 | "\\begin{align*}\n", 25 | "V_{t}(K_{1,t},K_{2,t}) & =\\max_{h_{1,t},h_{2,t}}U(c_{t},h_{1,t},h_{2,t})+\\beta V_{t+1}(K_{1,t+1},K_{2,t+1})\\\\\n", 26 | "c_{t} & =\\sum_{j=1}^{2}w_{j,t}h_{j,t}-T(w_{1,t}h_{1,t},w_{2,t}h_{2,t})\\\\\n", 27 | "\\log w_{j,t} & =\\alpha_{j,0}+\\alpha_{j,1}K_{j,t},\\;j\\in\\{1,2\\}\\\\\n", 28 | "K_{j,t+1} & =(1-\\delta)K_{j,t}+h_{j,t},\\;j\\in\\{1,2\\}\n", 29 | "\\end{align*}\n", 30 | "$$\n", 31 | "\n", 32 | "**Preferences** are sum of individuals\n", 33 | "$$\n", 34 | "U(c_{t},h_{1,t},h_{2,t})=2\\frac{(c_{t}/2)^{1+\\eta}}{1+\\eta}-\\rho_{1}\\frac{h_{1,t}^{1+\\gamma}}{1+\\gamma}-\\rho_{2}\\frac{h_{2,t}^{1+\\gamma}}{1+\\gamma}\n", 35 | "$$\n", 36 | "\n", 37 | "**Taxes** are on the household level\n", 38 | "$$\n", 39 | "T(Y_{1},Y_{2})=(1-\\lambda(Y_{1}+Y_{2})^{-\\tau})\\cdot(Y_{1}+Y_{2})\n", 40 | "$$\n", 41 | "\n", 42 | "**Terminal period:** There are no bequests such that\n", 43 | "$$\n", 44 | "V_{T}(K_{1,T},K_{2,T}) =\\max_{h_{1,T},h_{2,T}}U(c_{T},h_{1,T},h_{2,T})\n", 45 | "$$" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "## Setup" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 1, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "%load_ext autoreload\n", 62 | "%autoreload 2\n", 63 | "import numpy as np\n", 64 | "import numba as nb\n", 65 | "import matplotlib.pyplot as plt" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "# Consumption-Saving Model" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 2, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "# load local model file and initialize model class\n", 82 | "from DynHouseholdLaborModel import DynHouseholdLaborModelClass\n", 83 | "model = DynHouseholdLaborModelClass()\n", 84 | "\n", 85 | "par = model.par\n", 86 | "sol = model.sol\n", 87 | "sim = model.sim" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "## Estimation.\n", 95 | "Imagine that labor supply in the data is 0.7 for member 1 and 0.65 for member 2 in the first period (say, age 25)." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 3, 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "name": "stdout", 105 | "output_type": "stream", 106 | "text": [ 107 | "rho_1:0.040 rho_2:0.030 -> obj = 0.0353346\n" 108 | ] 109 | } 110 | ], 111 | "source": [ 112 | "theta_names = ('rho_1','rho_2') # names (list) of parameters to estimate\n", 113 | "theta = np.array([0.04,0.03]) # parameter vector (array)\n", 114 | "data_moments = np.array([0.7,0.65]) # moments to match (from Statistics Denmark, say)\n", 115 | "\n", 116 | "def obj_func(theta,theta_names,data_moments,model_in):\n", 117 | " model = model_in.copy() # safety measure\n", 118 | "\n", 119 | " # set the current parameters\n", 120 | " for p,par_name in enumerate(theta_names):\n", 121 | " setattr(model.par, par_name, theta[p])\n", 122 | " print(f'{par_name}:{theta[p]:2.3f} ',end='')\n", 123 | "\n", 124 | " # solve the model for these parameters\n", 125 | " model.solve()\n", 126 | "\n", 127 | " # simulate the model using this solution\n", 128 | " model.simulate()\n", 129 | "\n", 130 | " # calculate the relevant moments\n", 131 | " h1 = np.mean(model.sim.h1[:,0])\n", 132 | " h2 = np.mean(model.sim.h2[:,0])\n", 133 | " sim_moments = np.array([h1,h2])\n", 134 | "\n", 135 | " # return the the objective function\n", 136 | " W = np.eye(2)\n", 137 | " diff = data_moments - sim_moments\n", 138 | " obj = diff.T @ W @ diff\n", 139 | "\n", 140 | " print(f'-> obj = {obj:2.7f}')\n", 141 | " return obj\n", 142 | " \n", 143 | "# test objective function \n", 144 | "obj = obj_func(theta,theta_names,data_moments,model)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 4, 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "name": "stdout", 154 | "output_type": "stream", 155 | "text": [ 156 | "rho_1:0.050 rho_2:0.050 -> obj = 0.0032287\n", 157 | "rho_1:0.053 rho_2:0.050 -> obj = 0.0035925\n", 158 | "rho_1:0.050 rho_2:0.053 -> obj = 0.0020226\n", 159 | "rho_1:0.048 rho_2:0.053 -> obj = 0.0021687\n", 160 | "rho_1:0.048 rho_2:0.055 -> obj = 0.0015151\n", 161 | "rho_1:0.046 rho_2:0.058 -> obj = 0.0015704\n", 162 | "rho_1:0.050 rho_2:0.055 -> obj = 0.0012030\n", 163 | "rho_1:0.051 rho_2:0.056 -> obj = 0.0008633\n", 164 | "rho_1:0.049 rho_2:0.059 -> obj = 0.0007679\n", 165 | "rho_1:0.048 rho_2:0.062 -> obj = 0.0009409\n", 166 | "rho_1:0.052 rho_2:0.060 -> obj = 0.0002334\n", 167 | "rho_1:0.055 rho_2:0.062 -> obj = 0.0000629\n", 168 | "rho_1:0.052 rho_2:0.065 -> obj = 0.0001544\n", 169 | "rho_1:0.059 rho_2:0.069 -> obj = 0.0003107\n", 170 | "rho_1:0.056 rho_2:0.066 -> obj = 0.0000535\n", 171 | "rho_1:0.059 rho_2:0.064 -> obj = 0.0003838\n", 172 | "rho_1:0.054 rho_2:0.065 -> obj = 0.0000169\n", 173 | "rho_1:0.055 rho_2:0.068 -> obj = 0.0001994\n", 174 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000074\n", 175 | "rho_1:0.053 rho_2:0.062 -> obj = 0.0000688\n", 176 | "rho_1:0.055 rho_2:0.065 -> obj = 0.0000126\n", 177 | "rho_1:0.056 rho_2:0.065 -> obj = 0.0000583\n", 178 | "rho_1:0.055 rho_2:0.065 -> obj = 0.0000019\n", 179 | "rho_1:0.054 rho_2:0.063 -> obj = 0.0000108\n", 180 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000029\n", 181 | "rho_1:0.054 rho_2:0.065 -> obj = 0.0000087\n", 182 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000018\n", 183 | "rho_1:0.055 rho_2:0.065 -> obj = 0.0000041\n", 184 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000006\n", 185 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000116\n", 186 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000000\n", 187 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000009\n", 188 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000002\n", 189 | "rho_1:0.055 rho_2:0.065 -> obj = 0.0000013\n", 190 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000001\n", 191 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000001\n", 192 | "rho_1:0.055 rho_2:0.064 -> obj = 0.0000000\n" 193 | ] 194 | } 195 | ], 196 | "source": [ 197 | "# call an optimizer\n", 198 | "from scipy.optimize import minimize\n", 199 | "\n", 200 | "init_theta = np.array([0.05,0.05])\n", 201 | "res = minimize(obj_func,init_theta,args=(theta_names,data_moments,model),method='nelder-mead')" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 5, 207 | "metadata": {}, 208 | "outputs": [ 209 | { 210 | "data": { 211 | "text/plain": [ 212 | "array([0.05472504, 0.06430852])" 213 | ] 214 | }, 215 | "execution_count": 5, 216 | "metadata": {}, 217 | "output_type": "execute_result" 218 | } 219 | ], 220 | "source": [ 221 | "# estimated parameters\n", 222 | "res.x" 223 | ] 224 | } 225 | ], 226 | "metadata": { 227 | "kernelspec": { 228 | "display_name": "Python 3", 229 | "language": "python", 230 | "name": "python3" 231 | }, 232 | "language_info": { 233 | "codemirror_mode": { 234 | "name": "ipython", 235 | "version": 3 236 | }, 237 | "file_extension": ".py", 238 | "mimetype": "text/x-python", 239 | "name": "python", 240 | "nbconvert_exporter": "python", 241 | "pygments_lexer": "ipython3", 242 | "version": "3.8.10" 243 | }, 244 | "toc-autonumbering": true, 245 | "vscode": { 246 | "interpreter": { 247 | "hash": "2a1ca330d9582a7d9f549c991d1ebe88efa30325a2a9c927421566fc2176e6bd" 248 | } 249 | } 250 | }, 251 | "nbformat": 4, 252 | "nbformat_minor": 4 253 | } 254 | -------------------------------------------------------------------------------- /2024/12_expost/DynHouseholdLaborModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize, NonlinearConstraint 3 | import warnings 4 | warnings.filterwarnings("ignore", message="delta_grad == 0.0. Check if the approximated function is linear.") # turn of annoying warning 5 | 6 | from EconModel import EconModelClass 7 | 8 | from consav.grids import nonlinspace 9 | from consav.linear_interp import interp_2d 10 | 11 | class DynHouseholdLaborModelClass(EconModelClass): 12 | 13 | def settings(self): 14 | """ fundamental settings """ 15 | 16 | pass 17 | 18 | def setup(self): 19 | """ set baseline parameters """ 20 | 21 | # unpack 22 | par = self.par 23 | 24 | par.T = 10 # time periods 25 | 26 | # preferences 27 | par.beta = 0.98 # discount factor 28 | 29 | par.rho_1 = 0.05 # weight on labor dis-utility of men 30 | par.rho_2 = 0.05 # weight on labor dis-utility of women 31 | par.eta = -1.5 # CRRA coefficient 32 | par.gamma = 2.5 # curvature on labor hours 33 | 34 | par.mu = 0.5 # bargaining weight 35 | 36 | # income 37 | par.wage_const_1 = np.log(10_000.0) # constant, men 38 | par.wage_const_2 = np.log(10_000.0) # constant, women 39 | par.wage_K_1 = 0.1 # return on human capital, men 40 | par.wage_K_2 = 0.1 # return on human capital, women 41 | 42 | par.delta = 0.1 # depreciation in human capital 43 | 44 | # taxes 45 | par.tax_scale = 2.28 46 | par.tax_pow = 0.0861765 47 | 48 | par.tax_scale_indiv = 1.75 49 | par.tax_pow_indiv = 0.0646416 50 | 51 | # grids 52 | par.k_max = 20.0 # maximum point in wealth grid 53 | par.Nk = 20 #30 # number of grid points in wealth grid 54 | 55 | # simulation 56 | par.simT = par.T # number of periods 57 | par.simN = 1_000 # number of individuals 58 | 59 | # reform 60 | par.joint_tax = True 61 | 62 | 63 | def allocate(self): 64 | """ allocate model """ 65 | 66 | # unpack 67 | par = self.par 68 | sol = self.sol 69 | sim = self.sim 70 | 71 | par.simT = par.T 72 | 73 | # a. human capital grid 74 | par.k_grid = nonlinspace(0.0,par.k_max,par.Nk,1.1) 75 | 76 | # d. solution arrays 77 | shape = (par.T,par.Nk,par.Nk) 78 | sol.h1 = np.nan + np.zeros(shape) 79 | sol.h2 = np.nan + np.zeros(shape) 80 | sol.V = np.nan + np.zeros(shape) 81 | 82 | # e. simulation arrays 83 | shape = (par.simN,par.simT) 84 | sim.h1 = np.nan + np.zeros(shape) 85 | sim.h2 = np.nan + np.zeros(shape) 86 | sim.k1 = np.nan + np.zeros(shape) 87 | sim.k2 = np.nan + np.zeros(shape) 88 | 89 | sim.income1 = np.nan + np.zeros(shape) 90 | sim.income2 = np.nan + np.zeros(shape) 91 | 92 | sim.tax = np.nan + np.zeros(shape) 93 | 94 | # g. initialization 95 | sim.k1_init = np.zeros(par.simN) 96 | sim.k2_init = np.zeros(par.simN) 97 | 98 | 99 | ############ 100 | # Solution # 101 | def solve(self): 102 | 103 | # a. unpack 104 | par = self.par 105 | sol = self.sol 106 | 107 | # b. solve last period 108 | 109 | # c. loop backwards (over all periods) 110 | for t in reversed(range(par.T)): 111 | 112 | # i. loop over state variables: human capital for each household member 113 | for i_k1,capital1 in enumerate(par.k_grid): 114 | for i_k2,capital2 in enumerate(par.k_grid): 115 | idx = (t,i_k1,i_k2) 116 | 117 | # ii. find optimal consumption and hours at this level of wealth in this period t. 118 | if t==(par.T-1): # last period 119 | obj = lambda x: -self.util(x[0],x[1],capital1,capital2) 120 | 121 | else: 122 | obj = lambda x: - self.value_of_choice(x[0],x[1],capital1,capital2,sol.V[t+1]) 123 | 124 | # call optimizer 125 | bounds = [(0,np.inf) for i in range(2)] 126 | 127 | init_h = np.array([0.1,0.1]) 128 | if i_k1>0: 129 | init_h[0] = sol.h1[t,i_k1-1,i_k2] 130 | if i_k2>0: 131 | init_h[1] = sol.h2[t,i_k1,i_k2-1] 132 | 133 | res = minimize(obj,init_h,bounds=bounds) 134 | 135 | # store results 136 | sol.h1[idx] = res.x[0] 137 | sol.h2[idx] = res.x[1] 138 | sol.V[idx] = -res.fun 139 | 140 | 141 | def value_of_choice(self,hours1,hours2,capital1,capital2,V_next): 142 | 143 | # a. unpack 144 | par = self.par 145 | 146 | # b. current utility 147 | util = self.util(hours1,hours2,capital1,capital2) 148 | 149 | # c. continuation value 150 | k1_next = (1.0-par.delta)*capital1 + hours1 151 | k2_next = (1.0-par.delta)*capital2 + hours2 152 | V_next = interp_2d(par.k_grid,par.k_grid,V_next,k1_next,k2_next) 153 | 154 | # d. return value of choice 155 | return util + par.beta*V_next 156 | 157 | 158 | # relevant functions 159 | def consumption(self,hours1,hours2,capital1,capital2): 160 | par = self.par 161 | 162 | income1 = self.wage_func(capital1,1) * hours1 163 | income2 = self.wage_func(capital2,2) * hours2 164 | income_hh = income1+income2 165 | 166 | tax_hh = self.tax_func(income1,income2) 167 | 168 | return income_hh - tax_hh 169 | 170 | def wage_func(self,capital,sex): 171 | # before tax wage rate 172 | par = self.par 173 | 174 | constant = par.wage_const_1 175 | return_K = par.wage_K_1 176 | if sex>1: 177 | constant = par.wage_const_2 178 | return_K = par.wage_K_2 179 | 180 | return np.exp(constant + return_K * capital) 181 | 182 | def tax_func(self,income1,income2): 183 | par = self.par 184 | 185 | if par.joint_tax: 186 | tax = (1.0 - par.tax_scale*((income1+income2)**(-par.tax_pow)))*(income1 + income2) 187 | else: 188 | tax1 = (1.0 - par.tax_scale_indiv*((income1)**(-par.tax_pow_indiv)))*(income1) 189 | tax2 = (1.0 - par.tax_scale_indiv*((income2)**(-par.tax_pow_indiv)))*(income2) 190 | tax = tax1 + tax2 191 | 192 | return tax 193 | 194 | def util(self,hours1,hours2,capital1,capital2): 195 | par = self.par 196 | 197 | cons = self.consumption(hours1,hours2,capital1,capital2) 198 | 199 | util_cons1 = par.mu * (cons/2)**(1.0+par.eta) / (1.0+par.eta) 200 | util_cons2 = (1.0-par.mu) * (cons/2)**(1.0+par.eta) / (1.0+par.eta) 201 | util_hours1 = par.mu * par.rho_1*(hours1)**(1.0+par.gamma) / (1.0+par.gamma) 202 | util_hours2 = (1.0 - par.mu) * par.rho_2*(hours2)**(1.0+par.gamma) / (1.0+par.gamma) 203 | 204 | return util_cons1 + util_cons2 - util_hours1 - util_hours2 205 | 206 | ############## 207 | # Simulation # 208 | def simulate(self): 209 | 210 | # a. unpack 211 | par = self.par 212 | sol = self.sol 213 | sim = self.sim 214 | 215 | # b. loop over individuals and time 216 | for i in range(par.simN): 217 | 218 | # i. initialize states 219 | sim.k1[i,0] = sim.k1_init[i] 220 | sim.k2[i,0] = sim.k2_init[i] 221 | 222 | for t in range(par.simT): 223 | 224 | # ii. interpolate optimal hours 225 | idx_sol = t 226 | sim.h1[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h1[idx_sol],sim.k1[i,t],sim.k2[i,t]) 227 | sim.h2[i,t] = interp_2d(par.k_grid,par.k_grid,sol.h2[idx_sol],sim.k1[i,t],sim.k2[i,t]) 228 | 229 | # store income 230 | sim.income1[i,t] = self.wage_func(sim.k1[i,t],1)*sim.h1[i,t] 231 | sim.income2[i,t] = self.wage_func(sim.k2[i,t],2)*sim.h2[i,t] 232 | 233 | # store tax payments 234 | sim.tax[i,t] = self.tax_func(sim.income1[i,t],sim.income2[i,t]) 235 | 236 | # iii. store next-period states 237 | if t