Consumption-Saving model with Idiosyncratic Income Shocks#

The ``IndShockConsumerType`` class

# Initial imports and notebook setup, click arrow to show
from HARK.ConsumptionSaving.ConsIndShockModel import IndShockConsumerType
from HARK.utilities import plot_funcs_der, plot_funcs
import matplotlib.pyplot as plt
import numpy as np

mystr = lambda number: "{:.4f}".format(number)

The module HARK.ConsumptionSaving.ConsIndShockModel concerns consumption-saving models with idiosyncratic shocks to (non-capital) income. All of the models assume CRRA utility with geometric discounting, no bequest motive, and income shocks are fully transitory or fully permanent.

ConsIndShockModel includes: 1. A very basic “perfect foresight” model with no uncertainty. 2. A model with risk over transitory and permanent income shocks. 3. The model described in (2), with an interest rate for debt that differs from the interest rate for savings.

This notebook provides documentation for the second of these models. \(\newcommand{\CRRA}{\rho}\) \(\newcommand{\DiePrb}{\mathsf{D}}\) \(\newcommand{\PermGroFac}{\Gamma}\) \(\newcommand{\Rfree}{\mathsf{R}}\) \(\newcommand{\DiscFac}{\beta}\)

Statement of idiosyncratic income shocks model#

Suppose we want to solve a model like the one analyzed in BufferStockTheory, which has all the same features as the perfect foresight consumer, plus idiosyncratic shocks to income each period. Agents with this kind of model are represented by the class IndShockConsumerType.

Specifically, this type of consumer receives two income shocks at the beginning of each period: a completely transitory shock \(\newcommand{\tShkEmp}{\theta}{\tShkEmp_t}\) and a completely permanent shock \(\newcommand{\pShk}{\psi}{\pShk_t}\). Moreover, the agent is subject to borrowing a borrowing limit: the ratio of end-of-period assets \(A_t\) to permanent income \(P_t\) must be greater than \(\underline{a}\). As with the perfect foresight problem, this model is stated in terms of normalized variables, dividing all real variables by \(P_t\):

\begin{align*} v_t(m_t) &= \max_{c_t} u(c_t) + \DiscFac (1-\DiePrb_{t+1}) \mathbb{E}_{t} \left[ (\PermGroFac_{t+1}\psi_{t+1})^{1-\CRRA} v_{t+1}(m_{t+1}) \right], \\ a_t &= m_t - c_t, \\ a_t &\geq \text{$\underline{a}$}, \\ m_{t+1} &= \Rfree/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}, \\ (\psi_{t+1},\theta_{t+1}) &\sim F_{t+1}, \\ \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1, \\ u(c) &= \frac{c^{1-\rho}}{1-\rho}. \end{align*}

Solution method for IndShockConsumerType#

With the introduction of (non-trivial) risk, the idiosyncratic income shocks model has no closed form solution and must be solved numerically. The function solveConsIndShock solves the one period problem for the IndShockConsumerType class. To do so, HARK uses the original version of the endogenous grid method (EGM) first described here; see also the SolvingMicroDSOPs lecture notes.

Briefly, the transition equation for \(m_{t+1}\) can be substituted into the problem definition; the second term of the reformulated maximand represents “end of period value of assets” \(\mathfrak{v}_t(a_t)\) (“Gothic v”):

\begin{align*} v_t(m_t) &= \max_{c_t} {~} u(c_t) + \underbrace{\DiscFac (1-\DiePrb_{t+1}) \mathbb{E}_{t} \left[ (\PermGroFac_{t+1}\psi_{t+1})^{1-\CRRA} v_{t+1}(\Rfree/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}) \right]}_{\equiv \mathfrak{v}_t(a_t)}. \end{align*}

The first order condition with respect to \(c_t\) is thus simply:

\begin{align*} u^{\prime}(c_t) - \mathfrak{v}'_t(a_t) = 0 \Longrightarrow c_t^{-\CRRA} = \mathfrak{v}'_t(a_t) \Longrightarrow c_t = \mathfrak{v}'_t(a_t)^{-1/\CRRA}, \end{align*}

and the marginal value of end-of-period assets can be computed as:

\begin{align*} \mathfrak{v}'_t(a_t) = \DiscFac (1-\DiePrb_{t+1}) \mathbb{E}_{t} \left[ \Rfree (\PermGroFac_{t+1}\psi_{t+1})^{-\CRRA} v'_{t+1}(\Rfree/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}) \right]. \end{align*}

To solve the model, we choose an exogenous grid of \(a_t\) values that span the range of values that could plausibly be achieved, compute \(\mathfrak{v}'_t(a_t)\) at each of these points, calculate the value of consumption \(c_t\) whose marginal utility is consistent with the marginal value of assets, then find the endogenous \(m_t\) gridpoint as \(m_t = a_t + c_t\). The set of \((m_t,c_t)\) gridpoints is then interpolated to construct the consumption function.

Example parameter values to construct an instance of IndShockConsumerType#

In order to create an instance of IndShockConsumerType, the user must specify parameters that characterize the (age-varying) distribution of income shocks \(F_{t+1}\), the artificial borrowing constraint \(\underline{a}\), and the exogenous grid of end-of-period assets-above-minimum for use by EGM, along with all of the parameters for the perfect foresight model. The table below presents the complete list of parameter values required to instantiate an IndShockConsumerType, along with example values.




Example value



Intertemporal discount factor




Coefficient of relative risk aversion




Risk free interest factor



\(1 - \DiePrb_{t+1}\)

Survival probability





Permanent income growth factor





Standard deviation of log permanent income shocks





Number of discrete permanent income shocks




Standard deviation of log transitory income shocks





Number of discrete transitory income shocks




Probability of being unemployed and getting \(\theta=\underline{\theta}\)




Transitory shock when unemployed




Probability of being “unemployed” when retired




Transitory shock when “unemployed” and retired




Period of the lifecycle model when retirement begins




Minimum value in assets-above-minimum grid




Maximum value in assets-above-minimum grid




Number of points in base assets-above-minimum grid




Exponential nesting factor for base assets-above-minimum grid




Additional values to add to assets-above-minimum grid




Artificial borrowing constraint (normalized)




Indicator for whether \(\texttt{vFunc}\) should be computed




Indicator for whether \(\texttt{cFunc}\) should use cubic splines




Number of periods in this type’s “cycle”




Number of times the “cycle” occurs



IdiosyncDict = {
    # Parameters shared with the perfect foresight model
    "CRRA": 2.0,  # Coefficient of relative risk aversion
    "Rfree": 1.03,  # Interest factor on assets
    "DiscFac": 0.96,  # Intertemporal discount factor
    "LivPrb": [0.98],  # Survival probability
    "PermGroFac": [1.01],  # Permanent income growth factor
    # Parameters that specify the income distribution over the lifecycle
    "PermShkStd": [0.1],  # Standard deviation of log permanent shocks to income
    "PermShkCount": 7,  # Number of points in discrete approximation to permanent income shocks
    "TranShkStd": [0.2],  # Standard deviation of log transitory shocks to income
    "TranShkCount": 7,  # Number of points in discrete approximation to transitory income shocks
    "UnempPrb": 0.05,  # Probability of unemployment while working
    "IncUnemp": 0.3,  # Unemployment benefits replacement rate
    "UnempPrbRet": 0.0005,  # Probability of "unemployment" while retired
    "IncUnempRet": 0.0,  # "Unemployment" benefits when retired
    "T_retire": 0,  # Period of retirement (0 --> no retirement)
    "tax_rate": 0.0,  # Flat income tax rate (legacy parameter, will be removed in future)
    # Parameters for constructing the "assets above minimum" grid
    "aXtraMin": 0.001,  # Minimum end-of-period "assets above minimum" value
    "aXtraMax": 20,  # Maximum end-of-period "assets above minimum" value
    "aXtraCount": 48,  # Number of points in the base grid of "assets above minimum"
    "aXtraNestFac": 3,  # Exponential nesting factor when constructing "assets above minimum" grid
    "aXtraExtra": [None],  # Additional values to add to aXtraGrid
    # A few other paramaters
    "BoroCnstArt": 0.0,  # Artificial borrowing constraint; imposed minimum level of end-of period assets
    "vFuncBool": True,  # Whether to calculate the value function during solution
    "CubicBool": False,  # Preference shocks currently only compatible with linear cFunc
    "T_cycle": 1,  # Number of periods in the cycle for this agent type
    # Parameters only used in simulation
    "AgentCount": 10000,  # Number of agents of this type
    "T_sim": 120,  # Number of periods to simulate
    "aNrmInitMean": -6.0,  # Mean of log initial assets
    "aNrmInitStd": 1.0,  # Standard deviation of log initial assets
    "pLvlInitMean": 0.0,  # Mean of log initial permanent income
    "pLvlInitStd": 0.0,  # Standard deviation of log initial permanent income
    "PermGroFacAgg": 1.0,  # Aggregate permanent income growth factor
    "T_age": None,  # Age after which simulated agents are automatically killed

The distribution of permanent income shocks is specified as mean one lognormal, with an age-varying (underlying) standard deviation. The distribution of transitory income shocks is also mean one lognormal, but with an additional point mass representing unemployment; the transitory shocks are adjusted so that the distribution is still mean one. The continuous distributions are discretized with an equiprobable distribution.

Optionally, the user can specify the period when the individual retires and escapes essentially all income risk as T_retire; this can be turned off by setting the parameter to \(0\). In retirement, all permanent income shocks are turned off, and the only transitory shock is an “unemployment” shock, likely with small probability; this prevents the retired problem from degenerating into a perfect foresight model.

The grid of assets above minimum \(\texttt{aXtraGrid}\) is specified by its minimum and maximum level, the number of gridpoints, and the extent of exponential nesting. The greater the (integer) value of \(\texttt{aXtraNestFac}\), the more dense the gridpoints will be at the bottom of the grid (and more sparse near the top); setting \(\texttt{aXtraNestFac}\) to \(0\) will generate an evenly spaced grid of \(a_t\).

The artificial borrowing constraint \(\texttt{BoroCnstArt}\) can be set to None to turn it off.

It is not necessary to compute the value function in this model, and it is not computationally free to do so. You can choose whether the value function should be calculated and returned as part of the solution of the model with \(\texttt{vFuncBool}\). The consumption function will be constructed as a piecewise linear interpolation when \(\texttt{CubicBool}\) is False, and will be a piecewise cubic spline interpolator if True.

Solving and examining the solution of the idiosyncratic income shocks model#

The cell below creates an infinite horizon instance of IndShockConsumerType and solves its model by calling its solve method.

IndShockExample = IndShockConsumerType(**IdiosyncDict)
IndShockExample.cycles = 0  # Make this type have an infinite horizon
GPFRaw                 = 0.984539
GPFNrm                 = 0.993777
GPFAggLivPrb           = 0.964848
Thorn = APF            = 0.994384
PermGroFacAdj          = 1.000611
uInvEpShkuInv          = 0.990704
VAF                    = 0.932054
WRPF                   = 0.213705
DiscFacGPFNrmMax       = 0.972061
DiscFacGPFAggLivPrbMax = 1.010600

After solving the model, we can examine an element of this type’s \(\texttt{solution}\):

{'cFunc': <HARK.interpolation.LowerEnvelope object at 0x000001B28A538DF0>, 'vFunc': <HARK.interpolation.ValueFuncCRRA object at 0x000001B28A5394C0>, 'vPfunc': <HARK.interpolation.MargValueFuncCRRA object at 0x000001B28A538D00>, 'vPPfunc': <HARK.utilities.NullFunc object at 0x000001B28A539040>, 'mNrmMin': 0.0, 'hNrm': 44.991920196607595, 'MPCmin': 0.044536273404377116, 'MPCmax': 1.0, 'mNrmStE': 1.548816570507704, 'mNrmTrg': 1.5799173260214134}

The single-period solution to an idiosyncratic shocks consumer’s problem has all of the same attributes as in the perfect foresight model, with a couple additions. The solution can include the marginal marginal value of market resources function \(\texttt{vPPfunc}\), but this is only constructed if \(\texttt{CubicBool}\) is True, so that the MPC can be accurately computed; when it is False, then \(\texttt{vPPfunc}\) merely returns NaN everywhere.

The solveConsIndShock function calculates steady state market resources and stores it in the attribute \(\texttt{mNrmSS}\). This represents the steady state level of \(m_t\) if this period were to occur indefinitely, but with income shocks turned off. This is relevant in a “one period infinite horizon” model like we’ve specified here, but is less useful in a lifecycle model.

Let’s take a look at the consumption function by plotting it, along with its derivative (the MPC):

print("Consumption function for an idiosyncratic shocks consumer type:")
plot_funcs(IndShockExample.solution[0].cFunc, IndShockExample.solution[0].mNrmMin, 5)
print("Marginal propensity to consume for an idiosyncratic shocks consumer type:")
    IndShockExample.solution[0].cFunc, IndShockExample.solution[0].mNrmMin, 5
Consumption function for an idiosyncratic shocks consumer type:
Marginal propensity to consume for an idiosyncratic shocks consumer type:

The lower part of the consumption function is linear with a slope of 1, representing the constrained part of the consumption function where the consumer would like to consume more by borrowing– his marginal utility of consumption exceeds the marginal value of assets– but he is prevented from doing so by the artificial borrowing constraint.

The MPC is a step function, as the \(\texttt{cFunc}\) itself is a piecewise linear function; note the large jump in the MPC where the borrowing constraint begins to bind.

If you want to look at the interpolation nodes for the consumption function, these can be found by “digging into” attributes of \(\texttt{cFunc}\):

    "mNrmGrid for unconstrained cFunc is ",
    "cNrmGrid for unconstrained cFunc is ",
    "mNrmGrid for borrowing constrained cFunc is ",
    "cNrmGrid for borrowing constrained cFunc is ",
mNrmGrid for unconstrained cFunc is  [-0.25017509 -0.23682357 -0.04309334  0.08570877  0.19249704  0.28773035
  0.37527975  0.4572262   0.53458817  0.60902763  0.68157147  0.75266421
  0.82159155  0.89091324  0.96108615  1.03297006  1.10702535  1.18386894
  1.26405846  1.34797683  1.43498517  1.52575439  1.62247992  1.7264799
  1.83886328  1.96091089  2.09399047  2.23965509  2.39978161  2.57666105
  2.77296758  2.99185309  3.23706867  3.51312387  3.82546834  4.18073875
  4.58704087  5.05438059  5.59517832  6.2249668   6.96332613  7.83514604
  8.87231638 10.11613869 11.62057399 13.45688075 15.72024305 18.53939481
cNrmGrid for unconstrained cFunc is  [0.         0.01235151 0.18691037 0.29541926 0.38070319 0.45312275
 0.51644051 0.57261755 0.62253956 0.66772112 0.70902525 0.74671381
 0.77986847 0.81082056 0.8397706  0.86728992 0.89351353 0.91869035
 0.94296058 0.9662323  0.98732483 1.00628889 1.02460772 1.04277873
 1.06096172 1.07933575 1.0979846  1.11695897 1.13637025 1.15642563
 1.17732806 1.19928447 1.22251841 1.24729118 1.27390725 1.30273462
 1.33419368 1.3688058  1.40720505 1.45016983 1.49866721 1.55391366
 1.61742883 1.69119491 1.77777206 1.88053049 2.00400672 2.15448643
 2.3411553 ]
mNrmGrid for borrowing constrained cFunc is  [0. 1.]
cNrmGrid for borrowing constrained cFunc is  [0. 1.]

The consumption function in this model is an instance of LowerEnvelope1D, a class that takes an arbitrary number of 1D interpolants as arguments to its initialization method. When called, a LowerEnvelope1D evaluates each of its component functions and returns the lowest value. Here, the two component functions are the unconstrained consumption function– how the agent would consume if the artificial borrowing constraint did not exist for just this period– and the borrowing constrained consumption function– how much he would consume if the artificial borrowing constraint is binding.

The actual consumption function is the lower of these two functions, pointwise. We can see this by plotting the component functions on the same figure:

plot_funcs(IndShockExample.solution[0].cFunc.functions, -0.25, 5.0)

Simulating the idiosyncratic income shocks model#

In order to generate simulated data, an instance of IndShockConsumerType needs to know how many agents there are that share these particular parameters (and are thus ex ante homogeneous), the distribution of states for newly “born” agents, and how many periods to simulate. These simulation parameters are described in the table below, along with example values.



Example value

Number of consumers of this type



Number of periods to simulate



Mean of initial log (normalized) assets



Stdev of initial log (normalized) assets



Mean of initial log permanent income



Stdev of initial log permanent income



Aggregrate productivity growth factor



Age after which consumers are automatically killed



Here, we will simulate 10,000 consumers for 120 periods. All newly born agents will start with permanent income of exactly \(P_t = 1.0 = \exp(\texttt{pLvlInitMean})\), as \(\texttt{pLvlInitStd}\) has been set to zero; they will have essentially zero assets at birth, as \(\texttt{aNrmInitMean}\) is \(-6.0\); assets will be less than \(1\%\) of permanent income at birth.

These example parameter values were already passed as part of the parameter dictionary that we used to create IndShockExample, so it is ready to simulate. We need to set the track_vars attribute to indicate the variables for which we want to record a history.

IndShockExample.track_vars = ["aNrm", "mNrm", "cNrm", "pLvl"]
{'aNrm': array([[0.14588158, 0.14694881, 0.14608402, ..., 0.14639646, 0.14556721,
        [0.29165128, 0.08947919, 0.20514701, ..., 0.54880598, 0.29145961,
         0.        ],
        [0.39857842, 0.34160689, 0.19900494, ..., 0.90055047, 0.39844106,
        [0.62898711, 0.91585056, 0.99316497, ..., 0.1473222 , 0.80138509,
        [0.82250145, 0.9992893 , 0.14574918, ..., 0.38308901, 0.80320282,
        [0.68670932, 1.05066623, 0.55874065, ..., 0.33211136, 0.90310929,
 'mNrm': array([[1.00089046, 1.00261969, 1.00121848, ..., 1.00172472, 1.0003811 ,
         1.0045033 ],
        [1.22183137, 0.90689172, 1.09407455, ..., 1.56194994, 1.22155652,
         0.4401183 ],
        [1.37019743, 1.29243732, 1.08456511, ..., 1.98297865, 1.37001611,
        [1.66018223, 2.00077221, 2.09068659, ..., 1.00322471, 1.8665088 ,
        [1.8913676 , 2.09776566, 1.00067594, ..., 1.34975154, 1.86864867,
        [1.7300687 , 2.1568373 , 1.57420574, ..., 1.2792981 , 1.98595448,
 'cNrm': array([[0.85500888, 0.85567088, 0.85513446, ..., 0.85532826, 0.85481388,
        [0.93018009, 0.81741253, 0.88892754, ..., 1.01314396, 0.9300969 ,
         0.4401183 ],
        [0.97161901, 0.95083043, 0.88556016, ..., 1.08242818, 0.97157505,
        [1.03119512, 1.08492164, 1.09752162, ..., 0.8559025 , 1.0651237 ,
        [1.06886615, 1.09847636, 0.85492676, ..., 0.96666252, 1.06544586,
        [1.04335938, 1.10617107, 1.01546508, ..., 0.94718674, 1.08284519,
 'pLvl': array([[1.08875607, 1.08875607, 0.92780942, ..., 1.08875607, 1.17807023,
        [1.28263111, 1.01015813, 0.93246391, ..., 1.01015813, 1.38784946,
        [1.3964724 , 0.93723423, 0.90325499, ..., 0.97851549, 1.51102952,
        [0.80314355, 1.5679065 , 1.80721232, ..., 1.17807023, 0.71824341,
        [0.74516415, 1.63491511, 1.08875607, ..., 1.14116783, 0.69574483,
        [0.64004717, 1.58370229, 0.93517011, ..., 1.34437584, 0.64551861,

We can now look at the simulated data in aggregate or at the individual consumer level. Like in the perfect foresight model, we can plot average (normalized) market resources over time, as well as average consumption:

plt.plot(np.mean(IndShockExample.history["mNrm"], axis=1))
plt.ylabel("Mean market resources")

plt.plot(np.mean(IndShockExample.history["cNrm"], axis=1))
plt.ylabel("Mean consumption")

We could also plot individual consumption paths for some of the consumers– say, the first five:

plt.plot(IndShockExample.history["cNrm"][:, 0:5])
plt.ylabel("Individual consumption paths")