{ "cells": [ { "cell_type": "markdown", "id": "410a28c3", "metadata": {}, "source": [ "# Making Structural Estimates From Empirical Results\n", "\n", "[![badge](https://img.shields.io/badge/Launch%20using%20-Econ--ARK-blue)](https://econ-ark.org/materials/structural-estimates-from-empirical-mpcs-fagereng-et-al#launch)\n", "\n", "This notebook conducts a quick and dirty structural estimation based on Table 9 of [\"MPC Heterogeneity and Household Balance Sheets\" by Fagereng, Holm, and Natvik](https://economicdynamics.org/meetpapers/2017/paper_65.pdf) , who use Norweigian administrative data on income, household assets, and lottery winnings to examine the MPC from transitory income shocks (lottery prizes). Their Table 9 reports an estimated MPC broken down by quartiles of bank deposits and\n", "prize size; this table is reproduced here as $\\texttt{MPC\\_target\\_base}$. In this demo, we use the Table 9 estimates as targets in a simple structural estimation, seeking to minimize the sum of squared differences between simulated and estimated MPCs by changing the (uniform) distribution of discount factors. The essential question is how well their results be rationalized by a simple one-asset consumption-saving model. (Note that the paper was later published under a different [version](https://www.aeaweb.org/articles?id=10.1257/mac.20190211) which unfortunately excluded table 9.)\n", "\n", "\n", "The function that estimates discount factors includes several options for estimating different specifications:\n", "\n", "1. TypeCount : Integer number of discount factors in discrete distribution; can be set to 1 to turn off _ex ante_ heterogeneity (and to discover that the model has no chance to fit the data well without such heterogeneity).\n", "2. AdjFactor : Scaling factor for the target MPCs; user can try to fit estimated MPCs scaled down by (e.g.) 50%.\n", "3. T_kill : Maximum number of years the (perpetually young) agents are allowed to live. Because this is quick and dirty, it's also the number of periods to simulate.\n", "4. Splurge : Amount of lottery prize that an individual will automatically spend in a moment of excitement (perhaps ancient tradition in Norway requires a big party when you win the lottery), before beginning to behave according to the optimal consumption function. The patterns in Table 9 can be fit much better when this is set around \\$700 --> 0.7. That doesn't seem like an unreasonable amount of money to spend on a memorable party.\n", "5. do_secant : Boolean indicator for whether to use \"secant MPC\", which is average MPC over the range of the prize. MNW believes authors' regressions are estimating this rather than point MPC. When False, structural estimation uses point MPC after receiving prize. NB: This is incompatible with Splurge > 0.\n", "6. drop_corner : Boolean for whether to include target MPC in the top left corner, which is greater than 1. Authors discuss reasons why the MPC from a transitory shock *could* exceed 1. Option is included here because this target tends to push the estimate around a bit." ] }, { "cell_type": "code", "execution_count": 1, "id": "909c9eb3", "metadata": {}, "outputs": [], "source": [ "# Import python tools\n", "import numpy as np\n", "from copy import deepcopy" ] }, { "cell_type": "code", "execution_count": 2, "id": "e2d966f1", "metadata": {}, "outputs": [], "source": [ "# Import needed tools from HARK\n", "\n", "from HARK.distribution import Uniform\n", "from HARK.utilities import get_percentiles\n", "from HARK.estimation import minimize_nelder_mead\n", "from HARK.ConsumptionSaving.ConsIndShockModel import IndShockConsumerType\n", "\n", "\n", "init_infinite = {\n", " \"CRRA\": 1.0, # Coefficient of relative risk aversion\n", " \"Rfree\": 1.01 / (1.0 - 1.0 / 160.0), # Survival probability,\n", " # Permanent income growth factor (no perm growth),\n", " \"PermGroFac\": [1.000**0.25],\n", " \"PermGroFacAgg\": 1.0,\n", " \"BoroCnstArt\": 0.0,\n", " \"CubicBool\": False,\n", " \"vFuncBool\": False,\n", " \"PermShkStd\": [\n", " (0.01 * 4 / 11) ** 0.5\n", " ], # Standard deviation of permanent shocks to income\n", " \"PermShkCount\": 5, # Number of points in permanent income shock grid\n", " \"TranShkStd\": [\n", " (0.01 * 4) ** 0.5\n", " ], # Standard deviation of transitory shocks to income,\n", " \"TranShkCount\": 5, # Number of points in transitory income shock grid\n", " \"UnempPrb\": 0.07, # Probability of unemployment while working\n", " \"IncUnemp\": 0.15, # Unemployment benefit replacement rate\n", " \"UnempPrbRet\": 0.07,\n", " \"IncUnempRet\": 0.15,\n", " \"aXtraMin\": 0.00001, # Minimum end-of-period assets in grid\n", " \"aXtraMax\": 40, # Maximum end-of-period assets in grid\n", " \"aXtraCount\": 32, # Number of points in assets grid\n", " \"aXtraExtra\": [None],\n", " \"aXtraNestFac\": 3, # Number of times to 'exponentially nest' when constructing assets grid\n", " \"LivPrb\": [1.0 - 1.0 / 160.0], # Survival probability\n", " \"DiscFac\": 0.97, # Default intertemporal discount factor; dummy value, will be overwritten\n", " \"cycles\": 0,\n", " \"T_cycle\": 1,\n", " \"T_retire\": 0,\n", " # Number of periods to simulate (idiosyncratic shocks model, perpetual youth)\n", " \"T_sim\": 1200,\n", " \"T_age\": 400,\n", " \"IndL\": 10.0 / 9.0, # Labor supply per individual (constant),\n", " \"aNrmInitMean\": np.log(0.00001),\n", " \"aNrmInitStd\": 0.0,\n", " \"pLvlInitMean\": 0.0,\n", " \"pLvlInitStd\": 0.0,\n", " \"AgentCount\": 10000,\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "id": "7725b09b", "metadata": {}, "outputs": [], "source": [ "# Set key problem-specific parameters\n", "\n", "TypeCount = 8 # Number of consumer types with heterogeneous discount factors\n", "AdjFactor = 1.0 # Factor by which to scale all of MPCs in Table 9\n", "T_kill = 100 # Don't let agents live past this age\n", "Splurge = 0.0 # Consumers automatically spend this amount of any lottery prize\n", "do_secant = True # If True, calculate MPC by secant, else point MPC\n", "drop_corner = False # If True, ignore upper left corner when calculating distance" ] }, { "cell_type": "code", "execution_count": 4, "id": "20a186c1", "metadata": {}, "outputs": [], "source": [ "# Set standard HARK parameter values\n", "\n", "base_params = deepcopy(init_infinite)\n", "base_params[\"LivPrb\"] = [0.975]\n", "base_params[\"Rfree\"] = 1.04 / base_params[\"LivPrb\"][0]\n", "base_params[\"PermShkStd\"] = [0.1]\n", "base_params[\"TranShkStd\"] = [0.1]\n", "base_params[\n", " \"T_age\"\n", "] = T_kill # Kill off agents if they manage to achieve T_kill working years\n", "base_params[\"AgentCount\"] = 10000\n", "# From Table 1, in thousands of USD\n", "base_params[\"pLvlInitMean\"] = np.log(23.72)\n", "base_params[\n", " \"T_sim\"\n", "] = T_kill # No point simulating past when agents would be killed off" ] }, { "cell_type": "code", "execution_count": 5, "id": "e407d367", "metadata": {}, "outputs": [], "source": [ "# Define the MPC targets from Fagereng et al Table 9; element i,j is lottery quartile i, deposit quartile j\n", "\n", "MPC_target_base = np.array(\n", " [\n", " [1.047, 0.745, 0.720, 0.490],\n", " [0.762, 0.640, 0.559, 0.437],\n", " [0.663, 0.546, 0.390, 0.386],\n", " [0.354, 0.325, 0.242, 0.216],\n", " ]\n", ")\n", "MPC_target = AdjFactor * MPC_target_base" ] }, { "cell_type": "code", "execution_count": 6, "id": "e6c72ee1", "metadata": {}, "outputs": [], "source": [ "# Define the four lottery sizes, in thousands of USD; these are eyeballed centers/averages\n", "\n", "lottery_size = np.array([1.625, 3.3741, 7.129, 40.0])" ] }, { "cell_type": "code", "execution_count": 7, "id": "855dc5d1", "metadata": {}, "outputs": [], "source": [ "# Make several consumer types to be used during estimation\n", "\n", "BaseType = IndShockConsumerType(**base_params)\n", "EstTypeList = []\n", "for j in range(TypeCount):\n", " EstTypeList.append(deepcopy(BaseType))\n", " EstTypeList[-1].seed = j" ] }, { "cell_type": "code", "execution_count": 8, "id": "71094b09", "metadata": {}, "outputs": [], "source": [ "# Define the objective function\n", "def FagerengObjFunc(center, spread, verbose=False):\n", " \"\"\"\n", " Objective function for the quick and dirty structural estimation to fit\n", " Fagereng, Holm, and Natvik's Table 9 results with a basic infinite horizon\n", " consumption-saving model (with permanent and transitory income shocks).\n", "\n", " Parameters\n", " ----------\n", " center : float\n", " Center of the uniform distribution of discount factors.\n", " spread : float\n", " Width of the uniform distribution of discount factors.\n", " verbose : bool\n", " When True, print to screen MPC table for these parameters. When False,\n", " print (center, spread, distance).\n", "\n", " Returns\n", " -------\n", " distance : float\n", " Euclidean distance between simulated MPCs and (adjusted) Table 9 MPCs.\n", " \"\"\"\n", " # Give our consumer types the requested discount factor distribution\n", " beta_set = (\n", " Uniform(bot=center - spread, top=center + spread)\n", " .discretize(N=TypeCount)\n", " .atoms.flatten()\n", " )\n", " for j in range(TypeCount):\n", " EstTypeList[j].DiscFac = beta_set[j]\n", "\n", " # Solve and simulate all consumer types, then gather their wealth levels\n", " for EstType in EstTypeList:\n", " EstType.solve()\n", " EstType.initialize_sim()\n", " EstType.simulate()\n", " EstType.unpack(\"cFunc\")\n", " WealthNow = np.concatenate([ThisType.state_now[\"aLvl\"] for ThisType in EstTypeList])\n", "\n", " # Get wealth quartile cutoffs and distribute them to each consumer type\n", " quartile_cuts = get_percentiles(WealthNow, percentiles=[0.25, 0.50, 0.75])\n", " for ThisType in EstTypeList:\n", " WealthQ = np.zeros(ThisType.AgentCount, dtype=int)\n", " for n in range(3):\n", " WealthQ[ThisType.state_now[\"aLvl\"] > quartile_cuts[n]] += 1\n", " ThisType.WealthQ = WealthQ\n", "\n", " # Keep track of MPC sets in lists of lists of arrays\n", " MPC_set_list = [\n", " [[], [], [], []],\n", " [[], [], [], []],\n", " [[], [], [], []],\n", " [[], [], [], []],\n", " ]\n", "\n", " # Calculate the MPC for each of the four lottery sizes for all agents\n", " for ThisType in EstTypeList:\n", " ThisType.simulate(1)\n", " c_base = ThisType.controls[\"cNrm\"]\n", " MPC_this_type = np.zeros((ThisType.AgentCount, 4))\n", " for k in range(4): # Get MPC for all agents of this type\n", " Llvl = lottery_size[k]\n", " Lnrm = Llvl / ThisType.state_now[\"pLvl\"]\n", " if do_secant:\n", " SplurgeNrm = Splurge / ThisType.state_now[\"pLvl\"]\n", " mAdj = ThisType.state_now[\"mNrm\"] + Lnrm - SplurgeNrm\n", " cAdj = ThisType.cFunc[0](mAdj) + SplurgeNrm\n", " MPC_this_type[:, k] = (cAdj - c_base) / Lnrm\n", " else:\n", " mAdj = ThisType.state_now[\"mNrm\"] + Lnrm\n", " MPC_this_type[:, k] = cAdj = ThisType.cFunc[0].derivative(mAdj)\n", "\n", " # Sort the MPCs into the proper MPC sets\n", " for q in range(4):\n", " these = ThisType.WealthQ == q\n", " for k in range(4):\n", " MPC_set_list[k][q].append(MPC_this_type[these, k])\n", "\n", " # Calculate average within each MPC set\n", " simulated_MPC_means = np.zeros((4, 4))\n", " for k in range(4):\n", " for q in range(4):\n", " MPC_array = np.concatenate(MPC_set_list[k][q])\n", " simulated_MPC_means[k, q] = np.mean(MPC_array)\n", "\n", " # Calculate Euclidean distance between simulated MPC averages and Table 9 targets\n", " diff = simulated_MPC_means - MPC_target\n", " if drop_corner:\n", " diff[0, 0] = 0.0\n", " distance = np.sqrt(np.sum((diff) ** 2))\n", " if verbose:\n", " print(simulated_MPC_means)\n", " else:\n", " print(center, spread, distance)\n", " return distance" ] }, { "cell_type": "code", "execution_count": 9, "id": "90ca2c1e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.92 0.03 1.1196898999208702\n", "0.9660000000000001 0.03 1.8557370146227723\n", "0.92 0.0315 1.1205388789300472\n", "0.874 0.0315 0.7762009314641924\n", "0.8280000000000001 0.03225 0.6794629566371587\n", "0.8280000000000002 0.03075 0.6812764349781586\n", "0.7360000000000001 0.033 0.7910782500357642\n", "0.782 0.03225 0.701480126254866\n", "0.8740000000000001 0.03075 0.776917731194195\n", "0.805 0.031875 0.6784586313768969\n", "0.8049999999999998 0.033375 0.6766778040100674\n", "0.7934999999999997 0.03468750000000001 0.6842925413106543\n", "0.7819999999999998 0.033 0.7005977272821906\n", "0.8165 0.0324375 0.6747927160969063\n", "0.8164999999999997 0.033937499999999995 0.6730511351615501\n", "0.8222499999999997 0.03496874999999999 0.6729548684813017\n", "0.8337499999999999 0.03403125 0.6818984152434335\n", "0.8121874999999998 0.0335390625 0.6738051373790596\n", "0.8179374999999995 0.036070312499999986 0.6705629741922412\n", "0.8186562499999992 0.03788671874999998 0.6681951147478101\n", "0.8287187499999991 0.03931640624999997 0.6708742365404755\n", "0.8251249999999986 0.04223437499999996 0.664296531186165\n", "0.8265624999999979 0.04586718749999995 0.6598362485225537\n", "0.816499999999998 0.044437499999999956 0.6588191494676393\n", "0.8103906249999975 0.04699804687499996 0.6560435981007816\n", "0.8182968749999961 0.05497851562499993 0.6429622106958304\n", "0.8181171874999946 0.0635244140624999 0.6283719404390292\n", "0.8019453124999942 0.06465527343749991 0.6317158378919989\n", "0.8096718749999914 0.08118164062499984 0.5987059095800152\n", "0.8093124999999883 0.09827343749999978 0.5681565783197481\n", "0.8254843749999887 0.09714257812499977 0.5764414055133064\n", "0.8166796874999824 0.13189160156249966 0.5323676419241254\n", "0.8159609374999763 0.16607519531249954 0.5904223685318514\n", "0.800507812499982 0.13302246093749967 0.5163117765181638\n", "0.7880195312499785 0.15096240234374964 0.5047124560283467\n", "0.7953867187499726 0.18458056640624948 0.5617132527925662\n", "0.7988681640624766 0.16300378417968706 0.5108506441527563\n", "0.7702080078124727 0.182074584960937 0.5287581823368499\n", "0.78182592773435 0.16952883911132766 0.5070351369307667\n", "0.7709772949218519 0.15748745727539024 0.5280528475238496\n", "0.7918954467773205 0.16162470245361285 0.5017035822390945\n", "0.798089050292949 0.14305826568603486 0.5061860549357586\n", "0.7940232696532993 0.14967590904235806 0.5028256236143316\n", "0.7978991851806412 0.16033820915222124 0.5063050345595199\n", "0.7904894447326442 0.15330635404586754 0.5021984920087686\n", "0.7883616218566655 0.16525514745712233 0.5024660695309078\n", "0.7897770338058239 0.16136033785343126 0.5012012963060823\n", "0.7911830358505002 0.16967868626117658 0.5077537001985432\n", "0.7906628425121082 0.15739943709969478 0.5010198321545949\n", "0.7885444295406117 0.15713507249951317 0.501600543157581\n", "0.7893821838497889 0.15825747998803807 0.5011177990197709\n", "0.7902679925560732 0.1542965792343016 0.5019956034989637\n", "0.7898997734933861 0.15959439819864885 0.500990160961901\n", "0.7911804321557053 0.15873635531030555 0.5009410672976718\n", "0.7920795563086636 0.15897579297143927 0.5010530968741752\n", "0.7904173631369834 0.16093131640925962 0.5011231702147277\n", "0.790601472668327 0.158282406927086 0.5009608820931558\n", "0.7918821313306463 0.15742436403874271 0.5010716288897102\n", "0.7903953629527012 0.1590518896586723 0.5009804079463754\n", "0.7913865418713313 0.15796687257871925 0.5010066892811852\n", "0.7906431576823587 0.15878063538868403 0.500971752393584\n", "0.7911387471416738 0.15823812684870753 0.5009681043577296\n", "0.7910148497768451 0.15837375398370165 0.5009669832514088\n", "0.7907670550471874 0.1586450082536899 0.5009566726168533\n", "0.7913460145345657 0.15909895663690948 0.5009794669723293\n", "0.7907876081348867 0.15848654435454188 0.5009538516244135\n", "0.7912009852434047 0.1585778914111575 0.5009527296683954\n", "0.7915938092642233 0.15882770236692115 0.5009772796966896\n", "0.7909891584172208 0.15857183385763668 0.5009391718629084\n", "0.7909686053295214 0.15873029775678474 0.5009633296184927\n", "0.7911428902649338 0.1586159929975643 0.5009474487098768\n", "0.7910267003079923 0.15869219617037794 0.5009522371916169\n", "0.7911138427756984 0.1586350437907677 0.5009511323341336\n", "0.791084795286463 0.15865409458397112 0.5009431227606922\n", "0.7910660243410773 0.15859391342760049 0.5009656806073184\n", "Finished estimating for scaling factor of 1.0 and \"splurge amount\" of $0.0\n", "Optimal (beta,nabla) is [0.79098916 0.15857183], simulated MPCs are:\n", "[[0.77304235 0.68123007 0.56397879 0.40685965]\n", " [0.74299194 0.66281567 0.55224436 0.39833641]\n", " [0.70298354 0.63332607 0.529786 0.38134206]\n", " [0.56111798 0.50265972 0.41152609 0.294467 ]]\n", "Distance from Fagereng et al Table 9 is 0.5009391718629084\n" ] } ], "source": [ "# Conduct the estimation\n", "\n", "guess = [0.92, 0.03]\n", "\n", "\n", "def f_temp(x):\n", " return FagerengObjFunc(x[0], x[1])\n", "\n", "\n", "opt_params = minimize_nelder_mead(f_temp, guess, verbose=False)\n", "print(\n", " \"Finished estimating for scaling factor of \"\n", " + str(AdjFactor)\n", " + ' and \"splurge amount\" of $'\n", " + str(1000 * Splurge)\n", ")\n", "print(\"Optimal (beta,nabla) is \" + str(opt_params) + \", simulated MPCs are:\")\n", "dist = FagerengObjFunc(opt_params[0], opt_params[1], True)\n", "print(\"Distance from Fagereng et al Table 9 is \" + str(dist))" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "ExecuteTime,collapsed,code_folding,-autoscroll", "cell_metadata_json": true, "formats": "ipynb,py:percent", "notebook_metadata_filter": "all,-widgets,-varInspector" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false } }, "nbformat": 4, "nbformat_minor": 5 }