13. Tax Smoothing#
13.1. Overview#
This is a sister lecture to our lecture on consumption-smoothing.
By renaming variables, we obtain a version of a model “tax-smoothing model” that Robert Barro [Barro, 1979] used to explain why governments sometimes choose not to balance their budgets every period but instead use issue debt to smooth tax rates over time.
The government chooses a tax collection path that minimizes the present value of its costs of raising revenue.
The government minimizes those costs by smoothing tax collections over time and by issuing government debt during temporary surges in government expenditures.
The present value of government expenditures is at the core of the tax-smoothing model, so we’ll again use formulas presented in present value formulas.
We’ll again use the matrix multiplication and matrix inversion tools that we used in present value formulas.
13.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
A government exists at times
It chooses chooses a stream of tax collections
The model takes a government expenditure stream as an “exogenous” input that is somehow determined outside the model.
The government faces a gross interest rate of
The government can borrow or lend at interest rate
Let
be a positive integer that constitutes a time-horizon. be a sequence of government expenditures. be a sequence of government debt. be a sequence of tax collections. be a fixed gross one period interest rate. be a fixed discount factor. be a given initial level of government debt be a terminal condition.
The sequence of government debt
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
(This no-Ponzi condition ensures that the government ultimately pays off its debts – it can’t simply roll them over indefinitely.)
The government faces a sequence of budget constraints that constrain sequences
Equations (13.1) constitute
Given a sequence
The model follows the following logical flow:
start with an exogenous government expenditure sequence
, an initial government debt , and a candidate tax collection path .use the system of equations (13.1) for
to compute a path of government debtverify that
satisfies the terminal debt constraint .If it does, declare that the candidate path is budget feasible.
if the candidate tax path is not budget feasible, propose a different tax 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” tax paths
In general, there are many budget feasible tax paths
Among all budget-feasible tax paths, which one should a government choose?
To answer this question, we assess alternative budget feasible tax paths
where
This is called the “present value of revenue-raising costs” in [Barro, 1979].
The quadratic term
This creates an incentive for tax smoothing.
Indeed, we shall see that when
By smoother we mean tax rates that are as close as possible to being constant over time.
The preference for smooth tax paths that is built into the model gives it the name “tax-smoothing model”.
Or equivalently, we can transform this into the same problem as in the consumption-smoothing lecture by maximizing the welfare criterion:
Let’s dive in and do some calculations that will help us understand how the model works.
Here we use default parameters
We create a Python namedtuple
to store these parameters with default values.
TaxSmoothing = namedtuple("TaxSmoothing",
["R", "g1", "g2", "β_seq", "S"])
def create_tax_smoothing_model(R=1.01, g1=1, g2=1/2, S=65):
"""
Creates an instance of the tax smoothing model.
"""
β = 1/R
β_seq = np.array([β**i for i in range(S+1)])
return TaxSmoothing(R, g1, g2, β_seq, S)
13.3. Barro tax-smoothing model#
A key object is the present value of government expenditures at time
This sum represents the present value of all future government expenditures that must be financed.
Formally it resembles the present value calculations we saw in this QuantEcon lecture present values.
This present value calculation is crucial for determining the government’s total financing needs.
By iterating on equation (13.1) and imposing the terminal condition
it is possible to convert a sequence of budget constraints (13.1) into a single intertemporal constraint
Equation (13.4) says that the present value of tax collections must equal the sum of initial debt and the present value of government expenditures.
When
(Later we’ll present a “variational argument” that shows that this constant path minimizes
criterion (13.2) and maximizes (13.3) when
In this case, we can use the intertemporal budget constraint to write
Equation (13.5) is the tax-smoothing model in a nutshell.
13.4. Mechanics of tax-smoothing#
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 tax-smoothing model.
In the calculations below, we’ll set default values of
13.4.1. Step 1#
For a
13.4.2. Step 2#
Compute a constant tax rate
13.4.3. Step 3#
Use the system of equations (13.1) for
To do this, we transform 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 government must satisfy its intertemporal budget constraint and end with zero debt, 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, B0, G_seq):
R, S = model.R, model.S
# present value of government expenditures
h0 = model.β_seq @ G_seq # since β = 1/R
# optimal constant tax rate
T0 = (1 - 1/R) / (1 - (1/R)**(S+1)) * (B0 + h0)
T_seq = T0*np.ones(S+1)
A = np.diag(-R*np.ones(S), k=-1) + np.eye(S+1)
b = G_seq - T_seq
b[0] = b[0] + B0
B_seq = np.linalg.inv(A) @ (R * b)
B_seq = np.concatenate([[B0], B_seq])
return T_seq, B_seq, h0
We use an example where the government starts with initial debt
This represents the government’s initial debt burden.
The government expenditure process
The drop in government expenditures could reflect a change in spending requirements or demographic shifts.
# Initial debt
B0 = 2 # initial government debt
# Government expenditure process
G_seq = np.concatenate([np.ones(46), 4*np.ones(5), np.ones(15)])
tax_model = create_tax_smoothing_model()
T_seq, B_seq, h0 = compute_optimal(tax_model, B0, G_seq)
print('check B_S+1=0:',
np.abs(B_seq[-1] - 0) <= 1e-8)
check B_S+1=0: True
The graphs below show paths of government expenditures, tax collections, and government debt.
# Sequence length
S = tax_model.S
fig, axes = plt.subplots(1, 2, figsize=(12,5))
axes[0].plot(range(S+1), G_seq, label='expenditures', lw=2)
axes[0].plot(range(S+1), T_seq, label='tax', lw=2)
axes[1].plot(range(S+2), B_seq, label='debt', color='green', lw=2)
axes[0].set_ylabel(r'$T_t,G_t$')
axes[1].set_ylabel(r'$B_t$')
for ax in axes:
ax.plot(range(S+2), np.zeros(S+2), '--', lw=1, color='black')
ax.legend()
ax.set_xlabel(r'$t$')
plt.show()
Note that
We can evaluate cost criterion (13.2) which measures the total cost / welfare of taxation
def cost(model, T_seq):
β_seq, g1, g2 = model.β_seq, model.g1, model.g2
cost_seq = g1 * T_seq - g2/2 * T_seq**2
return - β_seq @ cost_seq
print('Cost:', cost(tax_model, T_seq))
def welfare(model, T_seq):
return - cost(model, T_seq)
print('Welfare:', welfare(tax_model, T_seq))
Cost: -41.46532630469102
Welfare: 41.46532630469102
13.4.4. Experiments#
In this section we describe how a tax sequence would optimally respond to different sequences of government expenditures.
First we create a function plot_ts
that generates graphs for different instances of the tax-smoothing model tax_model
.
This will help us avoid rewriting code to plot outcomes for different government expenditure sequences.
def plot_ts(model, # tax-smoothing model
B0, # initial government debt
G_seq # government expenditure process
):
# Compute optimal tax path
T_seq, B_seq, h0 = compute_optimal(model, B0, G_seq)
# Sequence length
S = model.S
fig, axes = plt.subplots(1, 2, figsize=(12,5))
axes[0].plot(range(S+1), G_seq, label='expenditures', lw=2)
axes[0].plot(range(S+1), T_seq, label='taxes', lw=2)
axes[1].plot(range(S+2), B_seq, label='debt', color='green', lw=2)
axes[0].set_ylabel(r'$T_t,G_t$')
axes[1].set_ylabel(r'$B_t$')
for ax in axes:
ax.plot(range(S+2), np.zeros(S+2), '--', lw=1, color='black')
ax.legend()
ax.set_xlabel(r'$t$')
plt.show()
In the experiments below, please study how tax and government debt sequences vary across different sequences for government expenditures.
13.4.4.1. Experiment 1: one-time spending shock#
We first assume a one-time spending shock of
We’ll make
13.4.4.2. Experiment 2: permanent expenditure shift#
Now we assume a permanent increase in government expenditures of
Again we can study positive and negative cases
# Positive temporary expenditure shift L = 0.5 when t >= 21
G_seq_pos = np.concatenate(
[np.ones(21), 1.5*np.ones(25), np.ones(20)])
plot_ts(tax_model, B0, G_seq_pos)
13.4.4.3. Experiment 3: delayed spending surge#
Now we simulate a
13.4.4.4. Experiment 4: growing expenditures#
Now we simulate a geometric
We first experiment with
# Geometric growth parameters where λ = 1.05
λ = 1.05
G_0 = 1
t_max = 46
# Generate geometric G sequence
geo_seq = λ ** np.arange(t_max) * G_0
G_seq_geo = np.concatenate(
[geo_seq, np.max(geo_seq)*np.ones(20)])
plot_ts(tax_model, B0, G_seq_geo)
Now we show the behavior when
λ = 0.95
geo_seq = λ ** np.arange(t_max) * G_0
G_seq_geo = np.concatenate(
[geo_seq, λ ** t_max * np.ones(20)])
plot_ts(tax_model, B0, G_seq_geo)
What happens with oscillating expenditures
13.4.5. Feasible Tax Variations#
We promised to justify our claim that a constant tax rate
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 tax paths are cost-minimizing / welfare-improving, we shall create an admissible tax path variation sequence
This equation says that the present value of admissible tax path variations must be zero.
So once again, we encounter a formula for the present value:
we require that the present value of tax path variations be zero to maintain budget balance.
Here we’ll restrict ourselves to a two-parameter class of admissible tax 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 tax path variations
def compute_variation(model, ξ1, ϕ, B0, G_seq, verbose=1):
R, S, β_seq = model.R, model.S, model.β_seq
ξ0 = ξ1*((1 - 1/R) / (1 - (1/R)**(S+1))) * ((1 - (ϕ/R)**(S+1)) / (1 - ϕ/R))
v_seq = np.array([(ξ1*ϕ**t - ξ0) for t in range(S+1)])
if verbose == 1:
print('check feasible:', np.isclose(β_seq @ v_seq, 0))
T_opt, _, _ = compute_optimal(model, B0, G_seq)
Tvar_seq = T_opt + v_seq
return Tvar_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)
wel_opt = welfare(tax_model, T_seq)
for i, param in enumerate(params):
ξ1, ϕ = param
print(f'variation {i}: ξ1={ξ1}, ϕ={ϕ}')
Tvar_seq = compute_variation(model=tax_model,
ξ1=ξ1, ϕ=ϕ, B0=B0,
G_seq=G_seq)
print(f'welfare={welfare(tax_model, Tvar_seq)}')
print(f'welfare < optimal: {welfare(tax_model, Tvar_seq) < wel_opt}')
print('-'*64)
if i % 2 == 0:
ls = '-.'
else:
ls = '-'
ax.plot(range(S+1), Tvar_seq, ls=ls,
color=colors[ξ1],
label=fr'$\xi_1 = {ξ1}, \phi = {ϕ}$')
plt.plot(range(S+1), T_seq,
color='orange', label=r'Optimal $\vec{T}$ ')
plt.legend()
plt.xlabel(r'$t$')
plt.ylabel(r'$T_t$')
plt.show()
variation 0: ξ1=0.01, ϕ=0.95
check feasible: True
welfare=41.46523217108914
welfare < optimal: True
----------------------------------------------------------------
variation 1: ξ1=0.01, ϕ=1.02
check feasible: True
welfare=41.46467728803246
welfare < optimal: True
----------------------------------------------------------------
variation 2: ξ1=0.05, ϕ=0.95
check feasible: True
welfare=41.46297296464396
welfare < optimal: True
----------------------------------------------------------------
variation 3: ξ1=0.05, ϕ=1.02
check feasible: True
welfare=41.44910088822694
welfare < optimal: True
----------------------------------------------------------------

We can even use the Python np.gradient
command to compute derivatives of cost with respect to our two parameters.
We are teaching the key idea beneath the calculus of variations.
First, we define the cost with respect to
def cost_rel(ξ1, ϕ):
"""
Compute cost of variation sequence
for given ϕ, ξ1 with a tax-smoothing model
"""
Tvar_seq = compute_variation(tax_model, ξ1=ξ1,
ϕ=ϕ, B0=B0,
G_seq=G_seq,
verbose=0)
return cost(tax_model, Tvar_seq)
# Vectorize the function to allow array input
cost_vec = np.vectorize(cost_rel)
Then we can visualize the relationship between cost and
ξ1_arr = np.linspace(-0.5, 0.5, 20)
plt.plot(ξ1_arr, cost_vec(ξ1_arr, 1.02))
plt.ylabel('cost')
plt.xlabel(r'$\xi_1$')
plt.show()
cost_grad = cost_vec(ξ1_arr, 1.02)
cost_grad = np.gradient(cost_grad)
plt.plot(ξ1_arr, cost_grad)
plt.ylabel('derivative of cost')
plt.xlabel(r'$\xi_1$')
plt.show()
The same can be done on