12. Consumption Smoothing#
12.1. Overview#
In this lecture, we’ll study a famous model of the “consumption function” that Milton Friedman [Friedman, 1956] and Robert Hall [Hall, 1978]) proposed to fit some empirical data patterns that the original Keynesian consumption function described in this QuantEcon lecture geometric series missed.
We’ll study what is often called the “consumption-smoothing model.”
We’ll use matrix multiplication and matrix inversion, the same tools that we used in this QuantEcon lecture present values.
Formulas presented in present value formulas are at the core of the consumption-smoothing model because we shall use them to define a consumer’s “human wealth”.
The key idea that inspired Milton Friedman was that a person’s non-financial income, i.e., his or her wages from working, can be viewed as a dividend stream from ‘‘human capital’’ and that standard asset-pricing formulas can be applied to compute ‘‘non-financial wealth’’ that capitalizes that earnings stream.
Note
As we’ll see in this QuantEcon lecture equalizing difference model, Milton Friedman had used this idea in his PhD thesis at Columbia University, eventually published as [Kuznets and Friedman, 1939] and [Friedman and Kuznets, 1945].
It will take a while for a “present value” or asset price explicitly to appear in this lecture, but when it does it will be a key actor.
12.2. Analysis#
As usual, we’ll start by importing some Python modules.
import numpy as np
import matplotlib.pyplot as plt
from collections import namedtuple
The model describes a consumer who lives from time
We usually think of the non-financial income stream as coming from the person’s earnings from supplying labor.
The model takes a non-financial income stream as an input, regarding it as “exogenous” in the sense that it is determined outside the model.
The consumer faces a gross interest rate of
Let
be a positive integer that constitutes a time-horizon. be an exogenous sequence of non-negative non-financial incomes . be a sequence of financial wealth. be a sequence of non-negative consumption rates. be a fixed gross one period rate of return on financial assets. be a fixed discount factor. be a given initial level of financial assets be a terminal condition on final assets.
The sequence of financial wealth
We require it to satisfy two boundary conditions:
it must equal an exogenous value
at timeit must equal or exceed an exogenous value
at time .
The terminal condition
(We’ll soon see that a utility maximizing consumer won’t want to die leaving positive assets, so she’ll arrange her affairs to make
The consumer faces a sequence of budget constraints that constrains sequences
Equations (12.1) constitute
Given a sequence
Our model has the following logical flow.
start with an exogenous non-financial income sequence
, an initial financial wealth , and a candidate consumption path .use the system of equations (12.1) for
to compute a path of financial wealthverify that
satisfies the terminal wealth constraint .If it does, declare that the candidate path is budget feasible.
if the candidate consumption path is not budget feasible, propose a less greedy consumption path and start over
Below, we’ll describe how to execute these steps using linear algebra – matrix inversion and multiplication.
The above procedure seems like a sensible way to find “budget-feasible” consumption paths
In general, there are many budget feasible consumption paths
Among all budget-feasible consumption paths, which one should a consumer want?
To answer this question, we shall eventually evaluate alternative budget feasible consumption paths
where
When
Indeed, we shall see that when
By smoother we mean as close as possible to being constant over time.
The preference for smooth consumption paths that is built into the model gives it the name “consumption-smoothing model”.
We’ll postpone verifying our claim that a constant consumption path is optimal when
Before doing that, let’s dive in and do some calculations that will help us understand how the model works in practice when we provide the consumer with some different streams on non-financial income.
Here we use default parameters
We create a Python namedtuple to store these parameters with default values.
ConsumptionSmoothing = namedtuple("ConsumptionSmoothing",
["R", "g1", "g2", "β_seq", "T"])
def create_consumption_smoothing_model(R=1.05, g1=1, g2=1/2, T=65):
β = 1/R
β_seq = np.array([β**i for i in range(T+1)])
return ConsumptionSmoothing(R, g1, g2,
β_seq, T)
12.3. Friedman-Hall consumption-smoothing model#
A key object is what Milton Friedman called “human” or “non-financial” wealth at time
Human or non-financial wealth at time
Formally it very much resembles the asset price that we computed in this QuantEcon lecture present values.
Indeed, this is why Milton Friedman called it “human capital”.
By iterating on equation (12.1) and imposing the terminal condition
it is possible to convert a sequence of budget constraints (12.1) into a single intertemporal constraint
Equation (12.3) says that the present value of the consumption stream equals the sum of financial and non-financial (or human) wealth.
Robert Hall [Hall, 1978] showed that when
(Later we’ll present a “variational argument” that shows that this constant path maximizes
criterion (12.2) when
In this case, we can use the intertemporal budget constraint to write
Equation (12.4) is the consumption-smoothing model in a nutshell.
12.4. Mechanics of consumption-smoothing model#
As promised, we’ll provide step-by-step instructions on how to use linear algebra, readily implemented in Python, to compute all objects in play in the consumption-smoothing model.
In the calculations below, we’ll set default values of
12.4.1. Step 1#
For a
12.4.2. Step 2#
Compute an time
12.4.3. Step 3#
Use the system of equations (12.1) for
To do this, we translate that system of difference equations into a single matrix equation as follows:
Multiply both sides by the inverse of the matrix on the left side to compute
Because we have built into our calculations that the consumer leaves the model with exactly zero assets, just barely satisfying the
terminal condition that
Let’s verify this with Python code.
First we implement the model with compute_optimal
def compute_optimal(model, a0, y_seq):
R, T = model.R, model.T
# non-financial wealth
h0 = model.β_seq @ y_seq # since β = 1/R
# c0
c0 = (1 - 1/R) / (1 - (1/R)**(T+1)) * (a0 + h0)
c_seq = c0*np.ones(T+1)
# verify
A = np.diag(-R*np.ones(T), k=-1) + np.eye(T+1)
b = y_seq - c_seq
b[0] = b[0] + a0
a_seq = np.linalg.inv(A) @ b
a_seq = np.concatenate([[a0], a_seq])
return c_seq, a_seq, h0
We use an example where the consumer inherits
This can be interpreted as student debt with which the consumer begins his or her working life.
The non-financial process
The drop in non-financial income late in life reflects retirement from work.
# Financial wealth
a0 = -2 # such as "student debt"
# non-financial Income process
y_seq = np.concatenate([np.ones(46), np.zeros(20)])
cs_model = create_consumption_smoothing_model()
c_seq, a_seq, h0 = compute_optimal(cs_model, a0, y_seq)
print('check a_T+1=0:',
np.abs(a_seq[-1] - 0) <= 1e-8)
check a_T+1=0: True
The graphs below show paths of non-financial income, consumption, and financial assets.
# Sequence length
T = cs_model.T
fig, axes = plt.subplots(1, 2, figsize=(12,5))
axes[0].plot(range(T+1), y_seq, label='non-financial income', lw=2)
axes[0].plot(range(T+1), c_seq, label='consumption', lw=2)
axes[1].plot(range(T+2), a_seq, label='financial wealth', color='green', lw=2)
axes[0].set_ylabel(r'$c_t,y_t$')
axes[1].set_ylabel(r'$a_t$')
for ax in axes:
ax.plot(range(T+2), np.zeros(T+2), '--', lw=1, color='black')
ax.legend()
ax.set_xlabel(r'$t$')
plt.show()
Note that
We can evaluate welfare criterion (12.2)
def welfare(model, c_seq):
β_seq, g1, g2 = model.β_seq, model.g1, model.g2
u_seq = g1 * c_seq - g2/2 * c_seq**2
return β_seq @ u_seq
print('Welfare:', welfare(cs_model, c_seq))
Welfare: 13.285050962183433
12.4.4. Experiments#
In this section we describe how a consumption sequence would optimally respond to different sequences sequences of non-financial income.
First we create a function plot_cs
that generates graphs for different instances of the consumption-smoothing model cs_model
.
This will help us avoid rewriting code to plot outcomes for different non-financial income sequences.
def plot_cs(model, # consumption-smoothing model
a0, # initial financial wealth
y_seq # non-financial income process
):
# Compute optimal consumption
c_seq, a_seq, h0 = compute_optimal(model, a0, y_seq)
# Sequence length
T = cs_model.T
fig, axes = plt.subplots(1, 2, figsize=(12,5))
axes[0].plot(range(T+1), y_seq, label='non-financial income', lw=2)
axes[0].plot(range(T+1), c_seq, label='consumption', lw=2)
axes[1].plot(range(T+2), a_seq, label='financial wealth', color='green', lw=2)
axes[0].set_ylabel(r'$c_t,y_t$')
axes[1].set_ylabel(r'$a_t$')
for ax in axes:
ax.plot(range(T+2), np.zeros(T+2), '--', lw=1, color='black')
ax.legend()
ax.set_xlabel(r'$t$')
plt.show()
In the experiments below, please study how consumption and financial asset sequences vary across different sequences for non-financial income.
12.4.4.1. Experiment 1: one-time gain/loss#
We first assume a one-time windfall of
We’ll make
# Windfall W_0 = 2.5
y_seq_pos = np.concatenate([np.ones(21), np.array([2.5]), np.ones(24), np.zeros(20)])
plot_cs(cs_model, a0, y_seq_pos)
12.4.4.2. Experiment 2: permanent wage gain/loss#
Now we assume a permanent increase in income of
Again we can study positive and negative cases
# Positive permanent income change W = 0.5 when t >= 21
y_seq_pos = np.concatenate(
[np.ones(21), 1.5*np.ones(25), np.zeros(20)])
plot_cs(cs_model, a0, y_seq_pos)
12.4.4.3. Experiment 3: a late starter#
Now we simulate a
12.4.4.4. Experiment 4: geometric earner#
Now we simulate a geometric
We first experiment with
# Geometric earner parameters where λ = 1.05
λ = 1.05
y_0 = 1
t_max = 46
# Generate geometric y sequence
geo_seq = λ ** np.arange(t_max) * y_0
y_seq_geo = np.concatenate(
[geo_seq, np.zeros(20)])
plot_cs(cs_model, a0, y_seq_geo)
Now we show the behavior when
λ = 0.95
geo_seq = λ ** np.arange(t_max) * y_0
y_seq_geo = np.concatenate(
[geo_seq, np.zeros(20)])
plot_cs(cs_model, a0, y_seq_geo)
What happens when
12.4.5. Feasible consumption variations#
We promised to justify our claim that when
Let’s do that now.
The approach we’ll take is an elementary example of the “calculus of variations”.
Let’s dive in and see what the key idea is.
To explore what types of consumption paths are welfare-improving, we shall create an admissible consumption path variation sequence
This equation says that the present value of admissible consumption path variations must be zero.
So once again, we encounter a formula for the present value of an “asset”:
we require that the present value of consumption path variations be zero.
Here we’ll restrict ourselves to a two-parameter class of admissible consumption path variations of the form
We say two and not three-parameter class because
Let’s compute that function.
We require
which implies that
which implies that
which implies that
This is our formula for
Key Idea: if
Given
Now let’s compute and plot consumption path variations
def compute_variation(model, ξ1, ϕ, a0, y_seq, verbose=1):
R, T, β_seq = model.R, model.T, model.β_seq
ξ0 = ξ1*((1 - 1/R) / (1 - (1/R)**(T+1))) * ((1 - (ϕ/R)**(T+1)) / (1 - ϕ/R))
v_seq = np.array([(ξ1*ϕ**t - ξ0) for t in range(T+1)])
if verbose == 1:
print('check feasible:', np.isclose(β_seq @ v_seq, 0)) # since β = 1/R
c_opt, _, _ = compute_optimal(model, a0, y_seq)
cvar_seq = c_opt + v_seq
return cvar_seq
We visualize variations for
fig, ax = plt.subplots()
ξ1s = [.01, .05]
ϕs= [.95, 1.02]
colors = {.01: 'tab:blue', .05: 'tab:green'}
params = np.array(np.meshgrid(ξ1s, ϕs)).T.reshape(-1, 2)
for i, param in enumerate(params):
ξ1, ϕ = param
print(f'variation {i}: ξ1={ξ1}, ϕ={ϕ}')
cvar_seq = compute_variation(model=cs_model,
ξ1=ξ1, ϕ=ϕ, a0=a0,
y_seq=y_seq)
print(f'welfare={welfare(cs_model, cvar_seq)}')
print('-'*64)
if i % 2 == 0:
ls = '-.'
else:
ls = '-'
ax.plot(range(T+1), cvar_seq, ls=ls,
color=colors[ξ1],
label=fr'$\xi_1 = {ξ1}, \phi = {ϕ}$')
plt.plot(range(T+1), c_seq,
color='orange', label=r'Optimal $\vec{c}$ ')
plt.legend()
plt.xlabel(r'$t$')
plt.ylabel(r'$c_t$')
plt.show()
variation 0: ξ1=0.01, ϕ=0.95
check feasible: True
welfare=13.285009346064836
----------------------------------------------------------------
variation 1: ξ1=0.01, ϕ=1.02
check feasible: True
welfare=13.28491163101544
----------------------------------------------------------------
variation 2: ξ1=0.05, ϕ=0.95
check feasible: True
welfare=13.284010559218512
----------------------------------------------------------------
variation 3: ξ1=0.05, ϕ=1.02
check feasible: True
welfare=13.28156768298361
----------------------------------------------------------------

We can even use the Python np.gradient
command to compute derivatives of welfare with respect to our two parameters.
(We are actually discovering the key idea beneath the calculus of variations.)
First, we define the welfare with respect to
def welfare_rel(ξ1, ϕ):
"""
Compute welfare of variation sequence
for given ϕ, ξ1 with a consumption-smoothing model
"""
cvar_seq = compute_variation(cs_model, ξ1=ξ1,
ϕ=ϕ, a0=a0,
y_seq=y_seq,
verbose=0)
return welfare(cs_model, cvar_seq)
# Vectorize the function to allow array input
welfare_vec = np.vectorize(welfare_rel)
Then we can visualize the relationship between welfare and
ξ1_arr = np.linspace(-0.5, 0.5, 20)
plt.plot(ξ1_arr, welfare_vec(ξ1_arr, 1.02))
plt.ylabel('welfare')
plt.xlabel(r'$\xi_1$')
plt.show()
welfare_grad = welfare_vec(ξ1_arr, 1.02)
welfare_grad = np.gradient(welfare_grad)
plt.plot(ξ1_arr, welfare_grad)
plt.ylabel('derivative of welfare')
plt.xlabel(r'$\xi_1$')
plt.show()
The same can be done on
ϕ_arr = np.linspace(-0.5, 0.5, 20)
plt.plot(ξ1_arr, welfare_vec(0.05, ϕ_arr))
plt.ylabel('welfare')
plt.xlabel(r'$\phi$')
plt.show()
welfare_grad = welfare_vec(0.05, ϕ_arr)
welfare_grad = np.gradient(welfare_grad)
plt.plot(ξ1_arr, welfare_grad)
plt.ylabel('derivative of welfare')
plt.xlabel(r'$\phi$')
plt.show()
12.5. Wrapping up the consumption-smoothing model#
The consumption-smoothing model of Milton Friedman [Friedman, 1956] and Robert Hall [Hall, 1978]) is a cornerstone of modern economics that has important ramifications for the size of the Keynesian “fiscal policy multiplier” that we described in QuantEcon lecture geometric series.
The consumption-smoothingmodel lowers the government expenditure multiplier relative to one implied by the original Keynesian consumption function presented in geometric series.
Friedman’s work opened the door to an enlightening literature on the aggregate consumption function and associated government expenditure multipliers that remains active today.
12.6. Appendix: solving difference equations with linear algebra#
In the preceding sections we have used linear algebra to solve a consumption-smoothing model.
The same tools from linear algebra – matrix multiplication and matrix inversion – can be used to study many other dynamic models.
We’ll conclude this lecture by giving a couple of examples.
We’ll describe a useful way of representing and “solving” linear difference equations.
To generate some
12.6.1. First-order difference equation#
We’ll start with a first-order linear difference equation for
where
We can cast this set of
Multiplying both sides of (12.5) by the inverse of the matrix on the left provides the solution
12.6.2. Second-order difference equation#
A second-order linear difference equation for
where now
As we did with the first-order difference equation, we can cast this set of
Multiplying both sides by inverse of the matrix on the left again provides the solution.
Exercise 12.2
As an exercise, we ask you to represent and solve a third-order linear difference equation. How many initial conditions must you specify?