40. Input-Output Models#

40.1. Overview#

This lecture requires the following imports and installs before we proceed.

!pip install quantecon_book_networks
!pip install quantecon
!pip install pandas-datareader
Hide code cell output
Collecting quantecon_book_networks
  Downloading quantecon_book_networks-1.4-py2.py3-none-any.whl.metadata (1.6 kB)
Requirement already satisfied: numpy in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (1.26.4)
Requirement already satisfied: scipy in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (1.13.1)
Requirement already satisfied: pandas in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (2.2.2)
Requirement already satisfied: matplotlib in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (3.9.2)
Requirement already satisfied: pandas-datareader in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (0.10.0)
Requirement already satisfied: networkx in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (3.3)
Requirement already satisfied: wbgapi in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon_book_networks) (1.0.12)
Requirement already satisfied: contourpy>=1.0.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (4.51.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (1.4.4)
Requirement already satisfied: packaging>=20.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (24.1)
Requirement already satisfied: pillow>=8 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (10.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (3.1.2)
Requirement already satisfied: python-dateutil>=2.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from matplotlib->quantecon_book_networks) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas->quantecon_book_networks) (2024.1)
Requirement already satisfied: tzdata>=2022.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas->quantecon_book_networks) (2023.3)
Requirement already satisfied: lxml in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas-datareader->quantecon_book_networks) (5.2.1)
Requirement already satisfied: requests>=2.19.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas-datareader->quantecon_book_networks) (2.32.3)
Requirement already satisfied: PyYAML in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from wbgapi->quantecon_book_networks) (6.0.1)
Requirement already satisfied: tabulate in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from wbgapi->quantecon_book_networks) (0.9.0)
Requirement already satisfied: six>=1.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from python-dateutil>=2.7->matplotlib->quantecon_book_networks) (1.16.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader->quantecon_book_networks) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader->quantecon_book_networks) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader->quantecon_book_networks) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader->quantecon_book_networks) (2024.8.30)
Downloading quantecon_book_networks-1.4-py2.py3-none-any.whl (365 kB)
Installing collected packages: quantecon_book_networks
Successfully installed quantecon_book_networks-1.4
Requirement already satisfied: quantecon in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (0.8.0)
Requirement already satisfied: numba>=0.49.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (0.60.0)
Requirement already satisfied: numpy>=1.17.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.26.4)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (2.32.3)
Requirement already satisfied: scipy>=1.5.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.1)
Requirement already satisfied: sympy in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.2)
Requirement already satisfied: llvmlite<0.44,>=0.43.0dev0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from numba>=0.49.0->quantecon) (0.43.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2024.8.30)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from sympy->quantecon) (1.3.0)
Requirement already satisfied: pandas-datareader in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (0.10.0)
Requirement already satisfied: lxml in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas-datareader) (5.2.1)
Requirement already satisfied: pandas>=0.23 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas-datareader) (2.2.2)
Requirement already satisfied: requests>=2.19.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas-datareader) (2.32.3)
Requirement already satisfied: numpy>=1.26.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas>=0.23->pandas-datareader) (1.26.4)
Requirement already satisfied: python-dateutil>=2.8.2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas>=0.23->pandas-datareader) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas>=0.23->pandas-datareader) (2024.1)
Requirement already satisfied: tzdata>=2022.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas>=0.23->pandas-datareader) (2023.3)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.19.0->pandas-datareader) (2024.8.30)
Requirement already satisfied: six>=1.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas>=0.23->pandas-datareader) (1.16.0)
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import quantecon_book_networks
import quantecon_book_networks.input_output as qbn_io
import quantecon_book_networks.plotting as qbn_plt
import quantecon_book_networks.data as qbn_data
import matplotlib as mpl
from matplotlib.patches import Polygon

quantecon_book_networks.config("matplotlib")
mpl.rcParams.update(mpl.rcParamsDefault)

The following figure illustrates a network of linkages among 15 sectors obtained from the US Bureau of Economic Analysis’s 2021 Input-Output Accounts Data.

Hide code cell content
def build_coefficient_matrices(Z, X):
    """
    Build coefficient matrices A and F from Z and X via

        A[i, j] = Z[i, j] / X[j]
        F[i, j] = Z[i, j] / X[i]

    """
    A, F = np.empty_like(Z), np.empty_like(Z)
    n = A.shape[0]
    for i in range(n):
        for j in range(n):
            A[i, j] = Z[i, j] / X[j]
            F[i, j] = Z[i, j] / X[i]

    return A, F

ch2_data = qbn_data.production()
codes = ch2_data["us_sectors_15"]["codes"]
Z = ch2_data["us_sectors_15"]["adjacency_matrix"]
X = ch2_data["us_sectors_15"]["total_industry_sales"]
A, F = build_coefficient_matrices(Z, X)
Hide code cell source
centrality = qbn_io.eigenvector_centrality(A)

# Remove self-loops
for i in range(A.shape[0]):
    A[i][i] = 0

fig, ax = plt.subplots(figsize=(8, 10))
plt.axis("off")
color_list = qbn_io.colorise_weights(centrality,beta=False)

qbn_plt.plot_graph(A, X, ax, codes,
              layout_type='spring',
              layout_seed=5432167,
              tol=0.0,
              node_color_list=color_list)

plt.show()
_images/a09cc809a4350b89f92871d5d0fe9e24fc415d8db079c0b0e9e1481709251a91.png

Fig. 40.1 US 15 sector production network#

Label

Sector

Label

Sector

Label

Sector

ag

Agriculture

wh

Wholesale

pr

Professional Services

mi

Mining

re

Retail

ed

Education & Health

ut

Utilities

tr

Transportation

ar

Arts & Entertainment

co

Construction

in

Information

ot

Other Services (exc govt)

ma

Manufacturing

fi

Finance

go

Government

An arrow from i to j means that some of sector i’s output serves as an input to production of sector j.

Economies are characterised by many such links.

A basic framework for their analysis is Leontief’s input-output model.

After introducing the input-output model, we describe some of its connections to linear programming lecture.

40.2. Input-output analysis#

Let

  • x0 be the amount of a single exogenous input to production, say labor

  • xj,j=1,n be the gross output of final good j

  • dj,j=1,n be the net output of final good j that is available for final consumption

  • zij be the quantity of good i allocated to be an input to producing good j for i=1,n, j=1,n

  • z0j be the quantity of labor allocated to producing good j.

  • aij be the number of units of good i required to produce one unit of good j, i=0,,n,j=1,n.

  • w>0 be an exogenous wage of labor, denominated in dollars per unit of labor

  • p be an n×1 vector of prices of produced goods i=1,,n.

The technology for producing good j{1,,n} is described by the Leontief function

xj=mini{0,,n}(zijaij)

40.2.1. Two goods#

To illustrate, we begin by setting n=2 and formulating the following network.

Hide code cell source
G = nx.DiGraph()

nodes= (1, 2, 'c')
edges = ((1, 1), (1, 2), (2, 1), (2, 2), (1, 'c'), (2, 'c'))
edges1 = ((1, 1), (1, 2), (2, 1), (2, 2), (1, 'c'))
edges2 = [(2,'c')]
G.add_nodes_from(nodes)
G.add_edges_from(edges)

pos_list = ([0, 0], [2, 0], [1, -1])
pos = dict(zip(G.nodes(), pos_list))

fig, ax = plt.subplots()
plt.axis("off")

nx.draw_networkx_nodes(G, pos=pos, node_size=800,
                       node_color='white', edgecolors='black')
nx.draw_networkx_labels(G, pos=pos)
nx.draw_networkx_edges(G,pos=pos, edgelist=edges1,
                       node_size=300, connectionstyle='arc3,rad=0.2',
                       arrowsize=10, min_target_margin=15)
nx.draw_networkx_edges(G, pos=pos, edgelist=edges2,
                       node_size=300, connectionstyle='arc3,rad=-0.2',
                       arrowsize=10, min_target_margin=15)

plt.text(0.055, 0.125, r'$z_{11}$')
plt.text(1.825, 0.125, r'$z_{22}$')
plt.text(0.955, 0.1, r'$z_{21}$')
plt.text(0.955, -0.125, r'$z_{12}$')
plt.text(0.325, -0.5, r'$d_{1}$')
plt.text(1.6, -0.5, r'$d_{2}$')

plt.show()
_images/c9c282cdc521360a6f44f1e3ec33143bdb1a166a775e621dbd013479195b960f.png

Feasible allocations must satisfy

(1a11)x1a12x2d1a21x1+(1a22)x2d2a01x1+a02x2x0

This can be graphically represented as follows.

Hide code cell source
fig, ax = plt.subplots()
ax.grid()

# Draw constraint lines
ax.hlines(0, -1, 400)
ax.vlines(0, -1, 200)

ax.plot(np.linspace(55, 380, 100), (50-0.9*np.linspace(55, 380, 100))/(-1.46), color="r")
ax.plot(np.linspace(-1, 400, 100), (60+0.16*np.linspace(-1, 400, 100))/0.83, color="r")
ax.plot(np.linspace(250, 395, 100), (62-0.04*np.linspace(250, 395, 100))/0.33, color="b")

ax.text(130, 38, r"$(1-a_{11})x_1 + a_{12}x_2 \geq d_1$", size=10)
ax.text(10, 105, r"$-a_{21}x_1 + (1-a_{22})x_2 \geq d_2$", size=10)
ax.text(150, 150, r"$a_{01}x_1 +a_{02}x_2 \leq x_0$", size=10)

# Draw the feasible region
feasible_set = Polygon(np.array([[301, 151],
                                 [368, 143],
                                 [250, 120]]),
                       color="cyan")
ax.add_patch(feasible_set)

# Draw the optimal solution
ax.plot(250, 120, "*", color="black")
ax.text(260, 115, "solution", size=10)

plt.show()
_images/4cc66d3103f7d85d80a7102140273e830eb53fc96a9419811e3948853cbf9da4.png

More generally, constraints on production are

(40.1)#(IA)xda0xx0

where A is the n×n matrix with typical element aij and a0=[a01a0n].

If we solve the first block of equations of (40.1) for gross output x we get

(40.2)#x=(IA)1dLd

where the matrix L=(IA)1 is sometimes called a Leontief Inverse.

To assure that the solution X of (40.2) is a positive vector, the following Hawkins-Simon conditions suffice:

det(IA)>0 and(IA)ij>0 for all i=j

Example 40.1

For example a two-good economy described by

(40.3)#A=[0.1400.010] and d=[502]
A = np.array([[0.1, 40],
             [0.01, 0]])
d = np.array([50, 2]).reshape((2, 1))
I = np.identity(2)
B = I - A
B
array([[ 9.e-01, -4.e+01],
       [-1.e-02,  1.e+00]])

Let’s check the Hawkins-Simon conditions

np.linalg.det(B) > 0 # checking Hawkins-Simon conditions
True

Now, let’s compute the Leontief inverse matrix

L = np.linalg.inv(B) # obtaining Leontief inverse matrix
L
array([[2.0e+00, 8.0e+01],
       [2.0e-02, 1.8e+00]])
x = L @ d   # solving for gross output
x
array([[260. ],
       [  4.6]])

40.3. Production possibility frontier#

The second equation of (40.1) can be written

a0x=x0

or

(40.4)#A0d=x0

where

A0=a0(IA)1

For i{1,,n}, the ith component of A0 is the amount of labor that is required to produce one unit of final output of good i.

Equation (40.4) sweeps out a production possibility frontier of final consumption bundles d that can be produced with exogenous labor input x0.

Example 40.2

Consider the example in (40.3).

Suppose we are now given

a0=[4100]

Then we can find A0 by

a0 = np.array([4, 100])
A0 = a0 @ L
A0
array([ 10., 500.])

Thus, the production possibility frontier for this economy is

10d1+500d2=x0

40.4. Prices#

[Dorfman et al., 1958] argue that relative prices of the n produced goods must satisfy

p1=a11p1+a21p2+a01wp2=a12p1+a22p2+a02w

More generally,

p=Ap+a0w

which states that the price of each final good equals the total cost of production, which consists of costs of intermediate inputs Ap plus costs of labor a0w.

This equation can be written as

(40.5)#(IA)p=a0w

which implies

p=(IA)1a0w

Notice how (40.5) with (40.1) forms a conjugate pair through the appearance of operators that are transposes of one another.

This connection surfaces again in a classic linear program and its dual.

40.5. Linear programs#

A primal problem is

minxwa0x

subject to

(IA)xd

The associated dual problem is

maxppd

subject to

(IA)pa0w

The primal problem chooses a feasible production plan to minimize costs for delivering a pre-assigned vector of final goods consumption d.

The dual problem chooses prices to maximize the value of a pre-assigned vector of final goods d subject to prices covering costs of production.

By the strong duality theorem, optimal value of the primal and dual problems coincide:

wa0x=pd

where ’s denote optimal choices for the primal and dual problems.

The dual problem can be graphically represented as follows.

Hide code cell source
fig, ax = plt.subplots()
ax.grid()

# Draw constraint lines
ax.hlines(0, -1, 50)
ax.vlines(0, -1, 250)

ax.plot(np.linspace(4.75, 49, 100), (4-0.9*np.linspace(4.75, 49, 100))/(-0.16), color="r")
ax.plot(np.linspace(0, 50, 100), (33+1.46*np.linspace(0, 50, 100))/0.83, color="r")

ax.text(15, 175, r"$(1-a_{11})p_1 - a_{21}p_2 \leq a_{01}w$", size=10)
ax.text(30, 85, r"$-a_{12}p_1 + (1-a_{22})p_2 \leq a_{02}w$", size=10)

# Draw the feasible region
feasible_set = Polygon(np.array([[17, 69],
                                 [4, 0],
                                 [0,0],
                                 [0, 40]]),
                       color="cyan")
ax.add_patch(feasible_set)

# Draw the optimal solution
ax.plot(17, 69, "*", color="black")
ax.text(18, 60, "dual solution", size=10)

plt.show()
_images/ef9d944904d52d5ce0fcb6ccea74b3245633be365287f4cd8d05af257e761a53.png

40.6. Leontief inverse#

We have discussed that gross output x is given by (40.2), where L is called the Leontief Inverse.

Recall the Neumann Series Lemma which states that L exists if the spectral radius r(A)<1.

In fact

L=i=0Ai

40.6.1. Demand shocks#

Consider the impact of a demand shock Δd which shifts demand from d0 to d1=d0+Δd.

Gross output shifts from x0=Ld0 to x1=Ld1.

If r(A)<1 then a solution exists and

Δx=LΔd=Δd+A(Δd)+A2(Δd)+

This illustrates that an element lij of L shows the total impact on sector i of a unit change in demand of good j.

40.7. Applications of graph theory#

We can further study input-output networks through applications of graph theory.

An input-output network can be represented by a weighted directed graph induced by the adjacency matrix A.

The set of nodes V=[n] is the list of sectors and the set of edges is given by

E={(i,j)V×V:aij>0}

In Fig. 40.1 weights are indicated by the widths of the arrows, which are proportional to the corresponding input-output coefficients.

We can now use centrality measures to rank sectors and discuss their importance relative to the other sectors.

40.7.1. Eigenvector centrality#

Eigenvector centrality of a node i is measured by

ei=1r(A)1jnaijej

We plot a bar graph of hub-based eigenvector centrality for the sectors represented in Fig. 40.1.

Hide code cell source
fig, ax = plt.subplots()
ax.bar(codes, centrality, color=color_list, alpha=0.6)
ax.set_ylabel("eigenvector centrality", fontsize=12)
plt.show()
_images/978086082c362356d877d184562f4b3210fb4487735d682ffcc320af47cf5e84.png

A higher measure indicates higher importance as a supplier.

As a result demand shocks in most sectors will significantly impact activity in sectors with high eigenvector centrality.

The above figure indicates that manufacturing is the most dominant sector in the US economy.

40.7.2. Output multipliers#

Another way to rank sectors in input-output networks is via output multipliers.

The output multiplier of sector j denoted by μj is usually defined as the total sector-wide impact of a unit change of demand in sector j.

Earlier when disussing demand shocks we concluded that for L=(lij) the element lij represents the impact on sector i of a unit change in demand in sector j.

Thus,

μj=j=1nlij

This can be written as μ=1L or

μ=1(IA)1

Please note that here we use 1 to represent a vector of ones.

High ranking sectors within this measure are important buyers of intermediate goods.

A demand shock in such sectors will cause a large impact on the whole production network.

The following figure displays the output multipliers for the sectors represented in Fig. 40.1.

Hide code cell source
A, F = build_coefficient_matrices(Z, X)
omult = qbn_io.katz_centrality(A, authority=True)

fig, ax = plt.subplots()
omult_color_list = qbn_io.colorise_weights(omult,beta=False)
ax.bar(codes, omult, color=omult_color_list, alpha=0.6)
ax.set_ylabel("Output multipliers", fontsize=12)
plt.show()
_images/b83564577a00a9940328b12114359c4a6b1a21feb902693116a027b63471fd89.png

We observe that manufacturing and agriculture are highest ranking sectors.

40.8. Exercises#

Exercise 40.1

[Dorfman et al., 1958] Chapter 9 discusses an example with the following parameter settings:

A=[0.11.460.160.17] and a0=[.04.33]
x=[250120] and x0=50
d=[5060]

Describe how they infer the input-output coefficients in A and a0 from the following hypothetical underlying “data” on agricultural and manufacturing industries:

z=[251754020] and z0=[1040]

where z0 is a vector of labor services used in each industry.

Exercise 40.2

Derive the production possibility frontier for the economy characterized in the previous exercise.