# @title Setup: libraries, chart colors and the data location
# Text after a "#" is a comment: Python ignores it, it is there for human readers.
# Step 1: load the libraries. `import` loads a library, a toolbox written by
# other people, and `as np` gives it a short nickname, so the code stays short.
import numpy as np # numbers and arrays
import pandas as pd # data tables
import plotly.express as px # quick charts from data tables
# charts built piece by piece (the model figures)
import plotly.graph_objects as go
import plotly.io as pio # settings shared by every chart
from plotly.subplots import make_subplots # several panels in one figure
# sliders, buttons and menus for the interactive figures
from ipywidgets import (interact, fixed, FloatSlider, SelectionSlider, IntRangeSlider, ToggleButtons,
Dropdown, SelectMultiple)
# Step 2: one light blue/green look for every chart
BLUE, TEAL, GREEN, GRAY = "#16528c", "#008070", "#7fb23a", "#8a96a3"
pio.templates.default = "plotly_white"
# the same chart size in Colab and on the website
pio.templates["plotly_white"].layout.update(width=860, height=480)
px.defaults.color_discrete_sequence = [BLUE, TEAL, GREEN, "#5aa0d8", "#0b4f47"]
WIDE = {"description_width": "initial"} # show the full slider labels
# Step 3, Colab only: let Plotly charts appear inside the interactive widgets.
# `try` runs the indented lines; if the import fails (we are not in Colab),
# `except` skips them.
try:
import google.colab
google.colab.output.enable_custom_widget_manager()
except ImportError:
pass # not running in Colab: nothing to do
# Step 4: the data files of this chapter live in the book's GitHub repository
DATA_URL = "https://raw.githubusercontent.com/quarcs-lab/macro102-site/main/data/ch04/"4. A Model of Production
Learning goals
Why are some countries so much richer than others? This chapter builds a first model to answer that question. By the end of it you will be able to:
- write down the Cobb-Douglas production function and derive, with the rules for exponents, its constant returns to scale and its diminishing marginal products;
- derive the marginal products and the firm’s hiring rules from profit maximization, and solve the model’s five equations step by step for the equilibrium wage, rental price of capital, output and output per person;
- show that labor gets two-thirds of output and capital one-third, so that firms make zero profit;
- read the comparative statics of the equilibrium as elasticities: what more capital, more workers or higher productivity do to output, the wage and the rental price;
- derive the development-accounting split of an income gap into a capital part and a TFP part, and compare the model’s predictions with data on 111 countries;
- measure total factor productivity (TFP) as the part of income the model cannot explain with capital;
- write Python functions with several arguments, combine Plotly Express charts with model curves, and select, filter and summarize rows of a large data table with pandas.
Model overview
Come back to Islandia, the small imaginary island country of this book. Its people make one all-purpose good, counted in tons per year, with two inputs: workers and machines (its capital: boats, nets, workshops). Think of all of Islandia’s firms as one company, Harbor Boatworks, that rents every machine and hires every worker on the island. How much can Islandia make with what it has, and how is the output split between the workers and the owners of the machines? And if Islandia is poorer than another country, is that because it has fewer machines, or because it uses them less well? The production model answers both questions.
The question. Why does a worker in a rich country produce many times more than a worker in a poor one?
The answer in one sentence. More machines per worker raise output, but with diminishing returns, so most of the gap between rich and poor countries comes from how well they combine their inputs: total factor productivity (TFP).
What goes in and what comes out. We choose productivity and the numbers of machines and workers. The model gives back output, the wage, the rental price of a machine, the income shares and output per person. Turned around, it measures TFP from data.
Road map.
- The production model: the assumptions, solving the model from the firm’s choice to the market prices, and what the model predicts.
- Development accounting: capital and TFP across countries: the assumptions, the steps that measure TFP and split an income gap, and what they show.
- Model in code: the key results as Python functions, checked with numbers. Every later section reuses them.
Notation. A bar over a letter marks a parameter: a number we take as given from outside the model. Letters without a bar are endogenous variables: the model determines them. In the code we simply write A, K_bar and L_bar.
| Symbol | Meaning | Type |
|---|---|---|
| \(Y, K, L\) | output (tons per year), machines and workers | endogenous |
| \(r, w\) | rental price of a machine and wage, both in tons of the good | endogenous |
| \(MPK, MPL\) | marginal products of capital and of labor | endogenous |
| \(\Pi\) | profit of the representative firm | endogenous |
| \(y, k\) | output and capital per person, \(Y/L\) and \(K/L\) | endogenous |
| \(\bar{A}\) | productivity; across countries we call it TFP | parameter |
| \(\bar{K}, \bar{L}\) | the machines and the workers Islandia has | parameters |
| \(\bar{k}\) | capital per person, \(\bar{K}/\bar{L}\) | parameter |
| \(\alpha\) | exponent on capital in the code (\(1/3\) in the text) | parameter |
| \(w^*, Y^*, \dots\) | an asterisk marks an equilibrium value | notation |
| \(\lambda\) | a factor that scales the inputs | notation |
The production model
Assumptions
Five rules describe Islandia’s economy in any given year.
1. One good. Islandia does not trade with other countries and makes a single good, counted in tons per year. We quote every price in tons of this good, so the good’s own price is 1. A good that serves as the yardstick for all other prices is called the numéraire.
2. Fixed amounts of the inputs. Islandia has \(\bar{L}\) workers and \(\bar{K}\) machines. Both are parameters: this chapter takes them as given and does not ask where they came from. (The Solow model of Chapter 5 explains how the machines pile up.)
3. Technology. With \(K\) machines and \(L\) workers, Harbor Boatworks can make
\[Y = F(K, L) = \bar{A}K^{1/3}L^{2/3}, \tag{1}\]
the Cobb-Douglas production function. The inputs \(K\) and \(L\) have no bars here, on purpose: the function gives the output of any mix of inputs, not only of the mix Islandia owns. The positive number \(\bar{A}\) is productivity. With the same machines and the same workers, a higher \(\bar{A}\) means more tons.
4. Price-taking firms that maximize profit. Islandia has many identical firms. Each is too small to move the wage \(w\) or the rental price of capital \(r\), so it takes both prices as given. Each picks \(K\) and \(L\) to earn the largest profit it can. With constant returns to scale (shown next), the number of firms does not matter. So we treat them as one representative firm, Harbor Boatworks, that rents all the machines and hires all the workers.
5. Markets clear. The wage and the rental price move until Harbor Boatworks wants exactly the machines and the workers that Islandia has.
Equation (1) has two properties that drive the whole chapter. Both come from the rules for exponents, \((ab)^c = a^cb^c\) and \(a^ca^d = a^{c+d}\) (Appendix C.3).
Constant returns to scale. Scale both inputs by the same factor \(\lambda > 0\):
\[F(\lambda K, \lambda L) = \bar{A}(\lambda K)^{1/3}(\lambda L)^{2/3} = \lambda^{1/3}\lambda^{2/3}\,\bar{A}K^{1/3}L^{2/3} = \lambda^{1/3+2/3}F(K,L) = \lambda F(K,L).\]
The exponents add up to one, so output rises in exact proportion to the inputs. A sum above one would give increasing returns to scale, and a sum below one decreasing returns to scale. Constant returns is a natural benchmark because a firm can always copy itself: if Harbor Boatworks opens a twin of its yard, with the same crew and machines, the twin makes as many tons as the first.
Diminishing returns to one input. Now scale the machines alone:
\[F(\lambda K, L) = \bar{A}(\lambda K)^{1/3}L^{2/3} = \lambda^{1/3}F(K,L).\]
Twice the machines with the same crew give only \(2^{1/3} \approx 1.26\) times the output. Each new machine has fewer hands to run it, so it adds less than the one before. Take \(\bar{A} = 1\) and the 64 workers of Fig. 1. One machine gives \(1^{1/3}\times 64^{2/3} = 16\) tons, and eight machines give \(8^{1/3}\times 64^{2/3} = 2\times 16 = 32\) tons. That is eight times the machines, but only twice the output.
Doubling machines and workers together doubles output, but adding machines alone runs into diminishing returns.
1. Harbor Boatworks has 64 workers, and \(\bar{A} = 1\). How many tons do 27 machines make? Compare the result with the 32 tons of 8 machines.
Answer
By equation (1), \(Y = 27^{1/3}\times 64^{2/3} = 3\times 16 = 48\) tons. The machines more than triple, from 8 to 27, but output grows only by the factor \(48/32 = 1.5\). That is diminishing returns to one input.
2. Islandia’s workforce and its stock of machines both double. What happens to output, and to output per worker?
Answer
Output doubles, by constant returns to scale: \(F(2K, 2L) = 2F(K, L)\). Output per worker, \(Y/L\), stays the same, because twice the tons are shared by twice the workers.
Solving the model
Step 1: the firm’s problem. Harbor Boatworks’ profit is what it sells minus what it pays for machines and workers. The good’s price is 1, so its sales are simply \(Y\):
\[\max_{K,L}\ \Pi = F(K,L) - rK - wL.\]
Step 2: the marginal products. The marginal product of capital, \(MPK\), is the extra output from one more machine, with the crew unchanged. The marginal product of labor, \(MPL\), is the extra output from one more worker, with the machines unchanged. For small changes, these are the partial derivatives of \(F\) (Appendix C.7). Hold \(L\) constant and apply the power rule, \(\frac{d}{dK}K^{a} = aK^{a-1}\):
\[MPK = \frac{\partial F}{\partial K} = \frac{1}{3}\bar{A}K^{1/3-1}L^{2/3} = \frac{1}{3}\bar{A}K^{-2/3}L^{2/3}.\]
Two rewritings make this easier to read. First, \(K^{-2/3}L^{2/3} = (L/K)^{2/3}\). Second, \(K^{-2/3} = K^{1/3}/K\), so \(\bar{A}K^{-2/3}L^{2/3} = \bar{A}K^{1/3}L^{2/3}/K = Y/K\). Together they give
\[MPK = \frac{1}{3}\bar{A}\left(\frac{L}{K}\right)^{2/3} = \frac{1}{3}\cdot\frac{Y}{K}.\]
Now hold \(K\) constant instead and use \(L^{2/3-1} = L^{-1/3}\):
\[MPL = \frac{\partial F}{\partial L} = \frac{2}{3}\bar{A}K^{1/3}L^{-1/3} = \frac{2}{3}\bar{A}\left(\frac{K}{L}\right)^{1/3} = \frac{2}{3}\cdot\frac{Y}{L}.\]
\[MPK = \frac{1}{3}\bar{A}\left(\frac{L}{K}\right)^{2/3} = \frac{1}{3}\cdot\frac{Y}{K} \tag{2}\]
\[MPL = \frac{2}{3}\bar{A}\left(\frac{K}{L}\right)^{1/3} = \frac{2}{3}\cdot\frac{Y}{L} \tag{3}\]
With a Cobb-Douglas function, each marginal product is the input’s exponent times its average product: tons per machine, \(Y/K\), or tons per worker, \(Y/L\).
The middle form of equation (2) shows diminishing returns at work. If Harbor Boatworks adds machines but keeps its crew, \(L/K\) falls, and so does the MPK. If it hires more workers instead, the MPK rises: every machine now has more hands to run it. Equation (3) tells the same story with the two inputs swapped.
Step 3: the hiring rules. At the best choice of inputs, the derivative of profit with respect to each input is zero (Appendix C.8):
\[\frac{\partial \Pi}{\partial K} = MPK - r = 0, \qquad \frac{\partial \Pi}{\partial L} = MPL - w = 0.\]
The same rule follows without calculus. If one more machine adds more tons than its rent, \(MPK > r\), renting it raises profit, so Harbor Boatworks rents more and the MPK falls. If \(MPK < r\), handing a machine back raises profit. Only at \(MPK = r\) is nothing left to gain. In the same way, the firm hires workers until \(MPL = w\). The MPK falls as \(K\) rises, so the rule \(r = \frac{1}{3}\bar{A}(L/K)^{2/3}\) is a downward-sloping demand curve for capital, with capital \(K\) on the x-axis and its rental price \(r\) on the y-axis. Likewise, the MPL curve is the demand curve for labor.
Step 4: five equations in five unknowns. The model has to find three quantities, \(Y\), \(K\) and \(L\), and two prices, \(r\) and \(w\):
| Equation | Role |
|---|---|
| \(Y = \bar{A}K^{1/3}L^{2/3}\) | production function |
| \(r = \frac{1}{3}\cdot\frac{Y}{K}\) | hiring rule for capital: demand for capital |
| \(w = \frac{2}{3}\cdot\frac{Y}{L}\) | hiring rule for labor: demand for labor |
| \(K = \bar{K}\) | the capital market clears |
| \(L = \bar{L}\) | the labor market clears |
The parameters are \(\bar{A}\), \(\bar{K}\) and \(\bar{L}\). Both input markets clear at the same time, so the solution is a general equilibrium, not the partial equilibrium of one market on its own.
Step 5: solve. The two market-clearing conditions give the quantities at once: \(K^* = \bar{K}\) and \(L^* = \bar{L}\). The supply curves are vertical lines at \(\bar{K}\) and \(\bar{L}\), so every machine and every islander is at work. Putting them into the production function gives output, \(Y^* = \bar{A}\bar{K}^{1/3}\bar{L}^{2/3}\). Putting \(Y^*\) into the hiring rules gives the prices. For the rental price, divide by \(\bar{K}\) and subtract exponents, \(\bar{K}^{1/3}/\bar{K} = \bar{K}^{-2/3}\):
\[r^* = \frac{1}{3}\cdot\frac{\bar{A}\bar{K}^{1/3}\bar{L}^{2/3}}{\bar{K}} = \frac{1}{3}\bar{A}\bar{K}^{-2/3}\bar{L}^{2/3} = \frac{1}{3}\bar{A}\left(\frac{\bar{L}}{\bar{K}}\right)^{2/3}.\]
For the wage, divide by \(\bar{L}\) and use \(\bar{L}^{2/3}/\bar{L} = \bar{L}^{-1/3}\):
\[w^* = \frac{2}{3}\cdot\frac{\bar{A}\bar{K}^{1/3}\bar{L}^{2/3}}{\bar{L}} = \frac{2}{3}\bar{A}\bar{K}^{1/3}\bar{L}^{-1/3} = \frac{2}{3}\bar{A}\left(\frac{\bar{K}}{\bar{L}}\right)^{1/3}.\]
We now collect the results, with each unknown on the left and only parameters on the right.
\[\begin{aligned} K^* &= \bar{K}, \qquad L^* = \bar{L}, \qquad Y^* = \bar{A}\bar{K}^{1/3}\bar{L}^{2/3},\\ r^* &= \frac{1}{3}\bar{A}\left(\frac{\bar{L}}{\bar{K}}\right)^{2/3}, \qquad w^* = \frac{2}{3}\bar{A}\left(\frac{\bar{K}}{\bar{L}}\right)^{1/3}. \end{aligned} \tag{4}\]
Islandia in numbers. With the values of Fig. 2, \(\bar{K} = \bar{L} = \bar{A} = 1\), Islandia makes \(Y^* = 1\) ton, a machine rents for \(r^* = 1/3\) of a ton and a worker earns \(w^* = 2/3\) of a ton.
No machine and no islander sits idle, so Islandia makes all it can. Each input is paid what its last unit adds: machines rent cheaply when they are plentiful, and workers earn more when each has many machines.
Step 6: who gets what. Multiply the wage by employment and divide by output. The hiring rule says \(w^* = \frac{2}{3}Y^*/L^*\), so \(L^*\) and \(Y^*\) cancel:
\[\frac{w^*L^*}{Y^*} = \frac{2}{3}\cdot\frac{Y^*}{L^*}\cdot\frac{L^*}{Y^*} = \frac{2}{3}, \qquad\text{and in the same way}\qquad \frac{r^*K^*}{Y^*} = \frac{1}{3}\cdot\frac{Y^*}{K^*}\cdot\frac{K^*}{Y^*} = \frac{1}{3}. \tag{5}\]
These are the factor shares of income. They equal the exponents of the production function, whatever the values of \(\bar{A}\), \(\bar{K}\) and \(\bar{L}\). This is why we put the exponent \(1/3\) on capital: in U.S. data, capital earns roughly a third of GDP and labor roughly two-thirds, and the split has moved little over time (Chapter 2). The two shares add up to one, \(2/3 + 1/3 = 1\). Multiply through by \(Y^*\):
\[w^*L^* + r^*K^* = Y^*. \tag{6}\]
Wages and rents together use up every ton Islandia makes, so economic profit is zero, \(\Pi^* = Y^* - r^*K^* - w^*L^* = 0\), as perfect competition leads us to expect. Equation (6) also says that total income equals total production, an identity of national income accounting; since all output is consumed, it equals spending too. What accountants call profit is the payment to capital, \(r^*K^* = Y^*/3\), and the stock market puts a price on claims to this flow of capital income (Fig. 3).
Step 7: per person. Living standards depend on output per person, \(y \equiv Y/L\). Let \(k \equiv K/L\) be capital per person. (Every islander works in this model, so “per person” and “per worker” mean the same thing.) Divide \(Y^*\) by \(\bar{L}\) and use \(\bar{L}^{2/3}/\bar{L} = \bar{L}^{-1/3}\):
\[y^* = \frac{Y^*}{L^*} = \frac{\bar{A}\bar{K}^{1/3}\bar{L}^{2/3}}{\bar{L}} = \bar{A}\bar{K}^{1/3}\bar{L}^{-1/3} = \bar{A}\left(\frac{\bar{K}}{\bar{L}}\right)^{1/3}.\]
Write \(\bar{k} = \bar{K}/\bar{L}\) for the machines per worker, and the result takes its shortest form.
\[y^* = \bar{A}\bar{k}^{1/3} \tag{7}\]
The prices can be written per person too: \(w^* = \frac{2}{3}y^*\), so the wage moves in step with income per person, and \(r^* = \frac{1}{3}y^*/\bar{k} = \frac{1}{3}\bar{A}\bar{k}^{-2/3}\).
Islandia in numbers. With the 8 machines and 64 workers of Fig. 1 and \(\bar{A} = 1\), \(\bar{k} = 1/8\). So \(y^* = (1/8)^{1/3} = 1/2\) ton per worker, and the wage is \(w^* = \frac{2}{3}\cdot\frac{1}{2} = 1/3\) of a ton.
An islander’s income rests on two things: how productive Islandia is, and how many machines each worker has. Productivity raises income one for one. More machines per worker raise it too, but the cube root means that each extra machine per worker adds less than the one before.
A general exponent. The code lets the exponent on capital be any \(\alpha\) between 0 and 1, so that \(Y = \bar{A}K^{\alpha}L^{1-\alpha}\). The same steps give \(MPK = \alpha Y/K\), \(MPL = (1-\alpha)Y/L\), factor shares \(\alpha\) and \(1-\alpha\), and \(y^* = \bar{A}\bar{k}^{\alpha}\). Setting \(\alpha = 1/3\) brings back equations (2) to (7).
Each input earns its marginal product, and these payments use up exactly all of Islandia’s output: one-third to machines, two-thirds to workers.
1. Harbor Boatworks runs 8 machines with 64 workers, and \(\bar{A} = 1\), so it makes 32 tons. What are the rental price and the wage? How many tons go to the workers, and how many to the owners of the machines?
Answer
By equation (2), \(r^* = \frac{1}{3}\cdot\frac{32}{8} = 4/3\) of a ton per machine. By equation (3), \(w^* = \frac{2}{3}\cdot\frac{32}{64} = 1/3\) of a ton per worker. Workers get \(64\times\frac{1}{3} \approx 21.3\) tons and machine owners get \(8\times\frac{4}{3} \approx 10.7\) tons: two-thirds and one-third of output (equation 5), adding up to all 32 tons (equation 6).
2. Suppose Harbor Boatworks paid a wage below the marginal product of labor. What would it want to do, and why can that not last?
Answer
Each extra worker would add more tons than the wage costs, so profit would rise with every hire, and the firm would want more workers than Islandia has. All islanders already work (\(L = \bar{L}\)), so firms compete for them and bid the wage up until \(w = MPL\), the hiring rule of step 3.
What the model predicts
Comparative statics. What happens to the equilibrium when a parameter changes? Take logs of the solution (4) and of equation (7) (Appendix C.4). The log of a product is the sum of the logs, and the log of a power is the exponent times the log:
\[\ln Y^* = \ln\bar{A} + \tfrac{1}{3}\ln\bar{K} + \tfrac{2}{3}\ln\bar{L}, \qquad \ln y^* = \ln\bar{A} + \tfrac{1}{3}\ln\bar{K} - \tfrac{1}{3}\ln\bar{L},\]
\[\ln w^* = \ln\tfrac{2}{3} + \ln\bar{A} + \tfrac{1}{3}\ln\bar{K} - \tfrac{1}{3}\ln\bar{L}, \qquad \ln r^* = \ln\tfrac{1}{3} + \ln\bar{A} - \tfrac{2}{3}\ln\bar{K} + \tfrac{2}{3}\ln\bar{L}.\]
Each coefficient is an elasticity: the percent change in the variable when the parameter rises by 1 percent.
| A 1% rise in … | \(Y^*\) | \(y^*\) | \(w^*\) | \(r^*\) | What moves in Fig. 2 |
|---|---|---|---|---|---|
| productivity \(\bar{A}\) | \(+1\%\) | \(+1\%\) | \(+1\%\) | \(+1\%\) | both demand curves (MPK and MPL) shift up |
| capital \(\bar{K}\) | \(+\tfrac{1}{3}\%\) | \(+\tfrac{1}{3}\%\) | \(+\tfrac{1}{3}\%\) | \(-\tfrac{2}{3}\%\) | the capital supply line shifts right; labor demand shifts up |
| labor \(\bar{L}\) | \(+\tfrac{2}{3}\%\) | \(-\tfrac{1}{3}\%\) | \(-\tfrac{1}{3}\%\) | \(+\tfrac{2}{3}\%\) | the labor supply line shifts right; capital demand shifts up |
| both \(\bar{K}\) and \(\bar{L}\) | \(+1\%\) | \(0\) | \(0\) | \(0\) | both supply lines shift right |
The factor shares of equation (5) stay at \(1/3\) and \(2/3\) in every row. For \(y^* = Y^*/\bar{L}\), the elasticity with respect to \(\bar{L}\) is \(2/3 - 1 = -1/3\), because the same output is now shared by more people.
What the table tells us about Islandia:
- Each input is worth more when the other one is plentiful. More machines lower their own rental price, a move down the capital demand curve, because of diminishing returns. But they raise the wage, since every worker now has more machines to use. More workers do the opposite.
- Prices depend only on the ratio \(\bar{K}/\bar{L}\). Scaling up the whole economy raises total output but changes nothing per person. A country is not richer per person just because it is bigger.
- Productivity is the one parameter that raises every outcome, each with an elasticity of 1.
- Capital per person has diminishing returns. By equation (7), doubling \(\bar{k}\) multiplies \(y^*\) by \(2^{1/3} \approx 1.26\). Large gaps in capital per person therefore turn into much smaller gaps in income. The second block takes this prediction to the data.
With the values of Fig. 2, doubling \(\bar{K}\) from 1 to 2 lowers the rental price from \(1/3 \approx 0.333\) to \(\frac{1}{3}(1/2)^{2/3} \approx 0.210\) and raises the wage from \(2/3 \approx 0.667\) to \(\frac{2}{3}\cdot 2^{1/3} \approx 0.840\).
More machines lower their own rent but raise wages, and productivity is the only parameter that lifts every outcome.
1. Islandia’s workforce grows by 10 percent, with the same machines and the same productivity. By roughly what percentage do the wage and the rental price change? Then compute the exact changes.
Answer
The elasticities with respect to \(\bar{L}\) are \(-1/3\) for \(w^*\) and \(+2/3\) for \(r^*\), so the wage falls by roughly 3.3 percent and the rental price rises by roughly 6.7 percent. Exactly, by equation (4), the wage is multiplied by \(1.1^{-1/3} \approx 0.969\), a fall of about 3.1 percent, and the rental price by \(1.1^{2/3} \approx 1.066\), a rise of about 6.6 percent. Each machine now has more hands, and each worker fewer machines.
2. Better know-how raises Islandia’s \(\bar{A}\) by 10 percent. Does the workers’ share of income rise?
Answer
No. The wage rises by 10 percent (elasticity 1), but so does output, so the labor share stays at \(w^*L^*/Y^* = 2/3\) (equation 5). Only the exponent of the production function can change the shares.
Development accounting: capital and TFP across countries
Assumptions
We now use equation (7) to compare whole countries. In the data, the benchmark is the United States. In our story, we compare Islandia with Mainland, which plays the role of the rich benchmark. This exercise is called development accounting, and it adds three assumptions:
- every country produces with the same Cobb-Douglas function, with the exponent \(1/3\) on capital that matches capital’s share of income in the data;
- \(y\) is measured as real GDP per person, and \(\bar{k}\) as the stock of physical capital (buildings, machinery, equipment, computers) per person;
- both are measured relative to the benchmark (the United States in the data, Mainland in the story), so the benchmark’s values are 1.
As a first pass, we also assume that productivity is the same everywhere, \(\bar{A} = 1\).
Solving the model
Step 1: compare two countries. Write equation (7) for a country \(i\) (Islandia, say) and for the benchmark, labeled \(US\) as in the data, and divide. A ratio of two powers with the same exponent is the power of the ratio, \(a^c/b^c = (a/b)^c\):
\[\frac{y^*_i}{y^*_{US}} = \frac{\bar{A}_i}{\bar{A}_{US}}\left(\frac{\bar{k}_i}{\bar{k}_{US}}\right)^{1/3}.\]
Step 2: equal productivity. If \(\bar{A}_i = \bar{A}_{US}\), and \(y\) and \(\bar{k}\) are both measured relative to the benchmark, the prediction is simply
\[y^* = \bar{k}^{1/3}.\]
In logs, \(\ln y^* = \frac{1}{3}\ln\bar{k}\): a gap in capital per person becomes an income gap only one-third as large in logs. Suppose Islandia has one-eighth of Mainland’s capital per person. The model then predicts that Islandia has \((1/8)^{1/3} = 1/2\) of Mainland’s income. With \(1/27\) of the capital, the prediction would be \(1/3\) of the income.
Each extra machine per worker adds less than the one before, so a country with far fewer machines is only somewhat poorer. If capital were the only difference, poor countries would be much richer than they are (Figs. 4 and 5).
Step 3: measure TFP. We have data on \(y\) and \(\bar{k}\) for every country, but no direct way to measure \(\bar{A}\). So we assume the model is right, and solve equation (7) for the one term we cannot see. Dividing both sides of \(y^* = \bar{A}\bar{k}^{1/3}\) by \(\bar{k}^{1/3}\):
\[\bar{A} = \frac{y^*}{\bar{k}^{1/3}} \tag{8}\]
This \(\bar{A}\) is total factor productivity (TFP): the output a country gets from given amounts of capital and labor.
Islandia in numbers. Suppose Islandia has one-eighth of Mainland’s capital per person and also one-eighth of its income. Its predicted income with Mainland’s TFP is \(1/2\), so its implied TFP is \((1/8)/(1/2) = 1/4\).
TFP is chosen so that the model fits exactly. That makes it a residual: it collects everything other than physical capital that raises income, and so it measures how much the model leaves unexplained.
Step 4: split an income gap. Write equation (7) for a rich country and a poor one, and divide them. The result splits the income gap into two factors.
\[\frac{y^*_{rich}}{y^*_{poor}} = \frac{\bar{A}_{rich}}{\bar{A}_{poor}}\times\left(\frac{\bar{k}_{rich}}{\bar{k}_{poor}}\right)^{1/3} \tag{9}\]
The first factor is the TFP part of the gap, and the second is the capital part.
Islandia in numbers. Mainland is 8 times richer than Islandia: \(8 = 4\times 2\), a factor of 4 from TFP and a factor of 2 from capital.
Taking logs turns the product into a sum, so the two parts of the log gap add up exactly:
\[\underbrace{\ln\frac{y^*_{rich}}{y^*_{poor}}}_{\text{income gap}} = \underbrace{\ln\frac{\bar{A}_{rich}}{\bar{A}_{poor}}}_{\text{TFP part}} + \underbrace{\tfrac{1}{3}\ln\frac{\bar{k}_{rich}}{\bar{k}_{poor}}}_{\text{capital part}}.\]
For Islandia and Mainland, capital explains \(\ln 2/\ln 8 = 1/3\) of the log gap, and TFP explains \(\ln 4/\ln 8 = 2/3\).
Step 5: the return to capital across countries. Per person, equation (2) reads \(MPK = \frac{1}{3}y/\bar{k} = \frac{1}{3}\bar{A}\bar{k}^{-2/3}\). Dividing for two countries,
\[\frac{MPK_{poor}}{MPK_{rich}} = \frac{\bar{A}_{poor}}{\bar{A}_{rich}}\left(\frac{\bar{k}_{rich}}{\bar{k}_{poor}}\right)^{2/3}.\]
With equal TFP, Islandia, with one-eighth of the capital, would have a marginal product \(8^{2/3} = 4\) times Mainland’s, and Mainland’s investors would rush to move machines to the island. With a TFP of \(1/4\), the two effects cancel exactly, \(\frac{1}{4}\times 4 = 1\), and a machine earns the same return in both places. Low TFP thus answers a question Robert Lucas asked in 1990: why does so little capital flow from rich to poor countries?
A capital gap shrinks to its cube root in income, so TFP has to carry most of the income gap.
1. Suppose instead that Islandia has \(1/27\) of Mainland’s capital per person and \(1/9\) of its income. What income does the model predict with equal TFP? What is Islandia’s implied TFP, and what share of the log income gap does capital explain?
Answer
With equal TFP, \(y^* = (1/27)^{1/3} = 1/3\). By equation (8), TFP is \((1/9)/(1/3) = 1/3\). By equation (9), the gap of 9 splits into \(3\times 3\): a factor of 3 from TFP and 3 from capital. In logs, capital explains \(\ln 3/\ln 9 = 1/2\) of the gap.
What the model predicts
| Result | Elasticity | In words |
|---|---|---|
| income, \(y^* = \bar{A}\bar{k}^{1/3}\) | \(1/3\) for \(\bar{k}\), \(1\) for \(\bar{A}\) | capital gaps are compressed; TFP gaps pass through in full |
| implied TFP, \(\bar{A} = y^*/\bar{k}^{1/3}\) | \(1\) for \(y^*\), \(-1/3\) for \(\bar{k}\) | a country poorer than its capital predicts has low TFP |
| return to capital, \(MPK = \frac{1}{3}\bar{A}\bar{k}^{-2/3}\) | \(-2/3\) for \(\bar{k}\), \(1\) for \(\bar{A}\) | capital-poor countries earn high returns only if their TFP is not too low |
- With equal productivity, the model fails in a telling way. It ranks countries correctly, but its income gaps are far too small: nearly every country is poorer than its capital predicts.
- A bigger exponent is no fix. With \(y^* = \bar{k}^{\alpha}\) and \(\alpha = 1\), income gaps would be as large as capital gaps. Then most poor countries fall below the 45-degree line, while most rich ones stay slightly above it. It also clashes with a capital share of about one-third (try it with the slider of Fig. 5).
- TFP explains most of the gap. When rich and poor countries are compared, TFP typically accounts for about two-thirds or more of the log income gap, and capital for the rest (Figs. 6 and 7 and the key numbers after them).
- What is TFP? The model takes \(\bar{A}\) as a parameter and does not explain it. The usual suspects are:
- human capital (skills and schooling);
- technology;
- institutions (secure property rights, courts that enforce contracts, and the rule of law);
- misallocation: capital and labor placed in the wrong firms.
Most of the income gap between rich and poor countries comes from TFP, which this model measures but does not explain.
1. Suppose Islandia’s statistics overstate its stock of machines. Is its measured TFP too high or too low?
Answer
Too low. Equation (8) divides income by \(\bar{k}^{1/3}\), so a capital number that is too large gives an \(\bar{A}\) that is too small. TFP is a residual, so every error in measuring capital ends up in it.
2. In the example of the previous check (\(1/27\) of Mainland’s capital, TFP of \(1/3\)), would Mainland’s investors want to move machines to Islandia?
Answer
Yes. By the formula of step 5, \(MPK_{Islandia}/MPK_{Mainland} = \frac{1}{3}\times 27^{2/3} = \frac{1}{3}\times 9 = 3\). Low TFP cuts the return on Islandia, but not by enough: a machine there still earns three times as much.
- Production with diminishing returns: \(Y = \bar{A}K^{1/3}L^{2/3}\) has constant returns to scale, but each extra machine adds less output than the one before (Fig. 1).
- Prices equal marginal products: the MPK and MPL curves are the demand curves, and the fixed supplies of machines and workers set \(r^*\) and \(w^*\) (Fig. 2).
- One-third to capital, two-thirds to labor: the payments use up all of output, profit is zero, and the stock market prices the income of capital (Fig. 3).
- Capital alone predicts too little inequality: with equal TFP, \(y^* = \bar{k}^{1/3}\), and nearly every country is poorer than its capital predicts (Fig. 5).
- TFP is the residual that closes the gap: \(\bar{A} = y^*/\bar{k}^{1/3}\), and it explains most of the income difference between rich and poor countries (Fig. 7).
Model in code
Now we turn the Key results into Python. The section has three parts: the setup (folded, because it is the same in every chapter), the model as functions, and numerical checks of the Key results. Run its cells once, in order. Every later section of the notebook reuses them.
Setup
Run the setup cells once, before anything else. They load the Python libraries, give every chart the same look, store the address of the chapter’s data and define two helpers that only the book website needs. You can use the notebook without reading them; open the box if you are curious.
The first cell loads the libraries and sets the look of the charts. Its comments explain each step.
The website cannot run Python, so each interactive figure also gets a version with a precomputed slider. The next two helpers build those versions. Run them and move on.
# @title Helper for the website versions (run this cell; you do not need to read it)
def slider_figure(make_figure, name, values, start, label=None):
"""Join precomputed versions of a figure with one slider (the website cannot run Python).
Arguments
make_figure : the function that draws the figure, e.g. production_curve
name : the argument the slider moves, e.g. "L"
values : the values of that argument to precompute
start : the value shown first
label : the slider's label (the argument name if None)
Returns
one Plotly figure with all versions and a slider that shows one at a time
"""
# Step 1: draw the figure once for every value
figures = [make_figure(**{name: v}) for v in values]
first = values.index(start)
# Step 2: copy every curve into one figure; only the start value's are visible
fig = go.Figure(layout=figures[first].layout)
owner = [] # which precomputed figure each trace is from
for i, f in enumerate(figures):
for trace in f.data:
trace.visible = (i == first)
fig.add_trace(trace)
owner.append(i)
# Step 3: one slider step per value: its curves, title, labels and shapes
steps = []
for i, f in enumerate(figures):
layout = {"title": f.layout.title.to_plotly_json(),
"annotations": [a.to_plotly_json() for a in f.layout.annotations],
"shapes": [s.to_plotly_json() for s in f.layout.shapes]}
visible = [o == i for o in owner]
steps.append(dict(method="update", label=str(values[i]), args=[{"visible": visible}, layout]))
fig.update_layout(sliders=[dict(active=first, steps=steps, pad=dict(t=60),
currentvalue=dict(prefix=(label or name) + " = "))])
return fig# @title Helper for the website versions with many countries (run this cell; you do not need to read it)
def country_menu(fig, shown, regions, label="Textbook countries"):
"""Draw the countries in `shown` and put every other country one click away in the legend.
With more than 40 countries, a menu above the plot chooses the region whose countries the legend lists.
Traces that are not countries stay as they are.
Arguments
fig : a Plotly figure with one trace per country
shown : the countries drawn at the start
regions : maps each country to its region
label : the name of the first menu entry
Returns
the same figure, with the visibility set and, for many countries, the region menu
"""
# Step 1: the menu entries, the textbook countries and then one entry per region
# the traces that are countries, and each of their regions once, A to Z
countries = [trace.name for trace in fig.data if trace.name in regions]
groups = [label] + sorted(set(regions[name] for name in countries))
if len(countries) <= 40:
groups = [None] # a short legend lists every country at once
# Step 2: for each entry, which traces are drawn, in the legend only, or hidden
buttons = []
for group in groups:
visible = []
for trace in fig.data:
if trace.name in shown:
visible.append(True) # a textbook country: drawn
elif trace.name not in regions:
# not a country: as it was
visible.append(True if trace.visible is None else trace.visible)
elif group is None or regions[trace.name] == group:
visible.append("legendonly") # in this region: a click away
else:
visible.append(False) # another region: hidden
buttons.append(dict(label=group, method="restyle", args=[{"visible": visible}]))
# Step 3: start with the first entry, and add the menu when there is more than one
for trace, v in zip(fig.data, buttons[0]["args"][0]["visible"]):
trace.visible = v
if len(groups) > 1:
menu = dict(type="dropdown", buttons=buttons, x=1, xanchor="right", y=1.02, yanchor="bottom")
fig.update_layout(updatemenus=list(fig.layout.updatemenus) + [menu], margin=dict(t=110))
return figThe model as functions
Three Key results hold the whole model: the production function, equation (1), and the marginal products, equations (2) and (3), which are also the equilibrium prices. We write each one as a Python function: a named recipe that takes inputs and gives back a result. To let you experiment with the shape of the function, the code writes the exponent on capital as \(\alpha\) (“alpha”), with the textbook value \(\alpha = 1/3\) as the default.
# `def` creates a function. Its inputs are the arguments in parentheses.
# `A=1.0` gives A a default value, used whenever we do not pass one.
# The text in triple quotes describes the function.
def production(K, L, A=1.0, alpha=1 / 3):
"""Output, from the Cobb-Douglas production function.
Equation (1): Y = A K^alpha L^(1 - alpha), with alpha = 1/3 in the textbook
Arguments
K : capital, the number of machines
L : labor, the number of workers
A : productivity, A-bar (1 unless we pass another value)
alpha : the exponent on capital (1/3 unless we pass another value)
Returns
output, Y
"""
# `return` gives back the result; `**` means "to the power of",
# and `1 / 3` is simply the number one third
return A * K ** alpha * L ** (1 - alpha)The marginal products are the extra output from one more machine and from one more worker. In equilibrium they are the rental price of capital and the wage. Both are a share of output per unit of the input.
def mpk(K, L, A=1.0, alpha=1 / 3):
"""Marginal product of capital, MPK.
Equation (2): MPK = alpha Y / K
Arguments
K, L : capital and labor
A : productivity, A-bar
alpha : the exponent on capital
Returns
the marginal product of capital, which is r* in equilibrium
"""
# one-third of output per machine
return alpha * production(K, L, A, alpha) / K
def mpl(K, L, A=1.0, alpha=1 / 3):
"""Marginal product of labor, MPL.
Equation (3): MPL = (1 - alpha) Y / L
Arguments
K, L : capital and labor
A : productivity, A-bar
alpha : the exponent on capital
Returns
the marginal product of labor, which is w* in equilibrium
"""
# two-thirds of output per worker
return (1 - alpha) * production(K, L, A, alpha) / LChecking the key results
The formulas came from algebra. Now we check them with numbers, one Key result at a time.
Check 1: the production function. With \(\bar{A} = 1\) and 64 workers, Harbor Boatworks should make 32 tons with 8 machines. Doubling both inputs should double output, and doubling capital alone should multiply it by only \(2^{1/3}\).
# Harbor Boatworks with 8 machines and 64 workers, then with more inputs.
# An f-string, f"... {value:.2f}", puts a value into the text;
# ":.2f" means "two decimals".
print(f"8 machines and 64 workers: Y = {production(8, 64):.2f} tons")
# constant returns to scale: double both inputs, double the output
print(f"Double both (16 and 128): Y = {production(16, 128):.2f} tons (constant returns: twice as much)")
# diminishing returns: double capital alone, output grows by only 2^(1/3)
print(f"Double capital only (16 and 64): Y = {production(16, 64):.2f} tons (2^(1/3) = {2 ** (1 / 3):.3f} times as much)")
# the 2nd machine adds more than the 8th
print(f"Gain from the 2nd machine: {production(2, 64) - production(1, 64):.2f} tons; "
f"from the 8th: {production(8, 64) - production(7, 64):.2f} tons")8 machines and 64 workers: Y = 32.00 tons
Double both (16 and 128): Y = 64.00 tons (constant returns: twice as much)
Double capital only (16 and 64): Y = 40.32 tons (2^(1/3) = 1.260 times as much)
Gain from the 2nd machine: 4.16 tons; from the 8th: 1.39 tons
Check 2: the marginal products. Equations (2) and (3) came from derivatives. A derivative is the extra output from a very small extra amount of an input, divided by that amount, so we can compute it a second way with a tiny step.
# Step 1: 8 machines, 64 workers, and a tiny step for the derivative
K, L, small = 8, 64, 0.0001
# Step 2: MPK in two forms of equation (2), then as (extra output) / (small step)
print(f"MPK, equation (2), (1/3) Y/K: {mpk(K, L):.4f}")
print(f"MPK, equation (2), (1/3) A (L/K)^(2/3): {(1 / 3) * (L / K) ** (2 / 3):.4f}")
print(f"MPK by a small step in K: {(production(K + small, L) - production(K, L)) / small:.4f}")
# Step 3: the same three ways for MPL, equation (3)
print(f"MPL, equation (3), (2/3) Y/L: {mpl(K, L):.4f}")
print(f"MPL, equation (3), (2/3) A (K/L)^(1/3): {(2 / 3) * (K / L) ** (1 / 3):.4f}")
print(f"MPL by a small step in L: {(production(K, L + small) - production(K, L)) / small:.4f}")MPK, equation (2), (1/3) Y/K: 1.3333
MPK, equation (2), (1/3) A (L/K)^(2/3): 1.3333
MPK by a small step in K: 1.3333
MPL, equation (3), (2/3) Y/L: 0.3333
MPL, equation (3), (2/3) A (K/L)^(1/3): 0.3333
MPL by a small step in L: 0.3333
Check 3: the equilibrium, the factor shares and zero profit. For three different economies, we compute the prices as marginal products and compare them with the closed forms of equation (4). Whatever the economy, capital should get one-third of output, labor two-thirds (equation 5), and nothing should be left as profit (equation 6).
# The `for` loop runs its indented lines once for each item of the list. Each item is
# a tuple, three numbers in parentheses, and `for K_bar, L_bar, A in` names them.
for K_bar, L_bar, A in [(1, 1, 1), (2, 1, 1), (8, 64, 1.5)]:
# Step 1: output and the prices r* = MPK and w* = MPL
Y_star = production(K_bar, L_bar, A)
r_star, w_star = mpk(K_bar, L_bar, A), mpl(K_bar, L_bar, A)
# Step 2: the same prices from the closed forms, and the profit left over
r_formula = (1 / 3) * A * (L_bar / K_bar) ** (2 / 3) # equation (4)
w_formula = (2 / 3) * A * (K_bar / L_bar) ** (1 / 3) # equation (4)
profit = Y_star - r_star * K_bar - w_star * L_bar # equation (6) says zero
# Step 3: print the prices, the shares (equation 5) and the profit. `abs` is the
# absolute value: it avoids printing a tiny rounding error as -0.000.
print(f"K = {K_bar}, L = {L_bar}, A = {A}: r* = {r_star:.3f} (formula {r_formula:.3f}), "
f"w* = {w_star:.3f} (formula {w_formula:.3f})")
print(f" capital share {r_star * K_bar / Y_star:.3f}, labor share {w_star * L_bar / Y_star:.3f}, "
f"profit {abs(profit):.3f}")K = 1, L = 1, A = 1: r* = 0.333 (formula 0.333), w* = 0.667 (formula 0.667)
capital share 0.333, labor share 0.667, profit 0.000
K = 2, L = 1, A = 1: r* = 0.210 (formula 0.210), w* = 0.840 (formula 0.840)
capital share 0.333, labor share 0.667, profit 0.000
K = 8, L = 64, A = 1.5: r* = 2.000 (formula 2.000), w* = 0.500 (formula 0.500)
capital share 0.333, labor share 0.667, profit 0.000
Check 4: income per person. Equation (7) says that output per worker depends only on capital per worker, and the wage is two-thirds of it. We check both for the economy with 8 machines and 64 workers, so \(\bar{k} = 1/8\).
# Step 1: output per worker, y* = Y*/L, with 8 machines and 64 workers
K_bar, L_bar = 8, 64
y_star = production(K_bar, L_bar) / L_bar
# Step 2: y* from capital per worker alone, equation (7)
print(f"y* = Y*/L: {y_star:.4f}")
print(f"y* = A k^(1/3), equation (7): {(K_bar / L_bar) ** (1 / 3):.4f}")
# Step 3: the wage is the MPL, and also two-thirds of y*
print(f"w* = MPL: {mpl(K_bar, L_bar):.4f}")
print(f"w* = (2/3) y*: {(2 / 3) * y_star:.4f}")y* = Y*/L: 0.5000
y* = A k^(1/3), equation (7): 0.5000
w* = MPL: 0.3333
w* = (2/3) y*: 0.3333
Check 5: the comparative statics. Starting from \(\bar{K} = \bar{L} = \bar{A} = 1\), we raise one parameter at a time by 1 percent. The table of elasticities predicts \(+1\%\) for everything when \(\bar{A}\) rises; \(+1/3\%\), \(+1/3\%\), \(+1/3\%\) and \(-2/3\%\) for \(Y^*\), \(y^*\), \(w^*\) and \(r^*\) when \(\bar{K}\) rises; and \(+2/3\%\), \(-1/3\%\), \(-1/3\%\) and \(+2/3\%\) when \(\bar{L}\) rises.
# Step 1: the starting point; y0 = Y0 because L = 1
Y0, w0, r0 = production(1, 1), mpl(1, 1), mpk(1, 1)
# Step 2: raise A, then K, then L by 1%, and recompute the equilibrium
for name, K_bar, L_bar, A in [("A", 1, 1, 1.01), ("K", 1.01, 1, 1), ("L", 1, 1.01, 1)]:
Y_star = production(K_bar, L_bar, A)
y_star = Y_star / L_bar
w_star, r_star = mpl(K_bar, L_bar, A), mpk(K_bar, L_bar, A)
# Step 3: percentage changes; ":+.3f" shows the sign and three decimals
print(f"1% higher {name}: Y* {100 * (Y_star / Y0 - 1):+.3f}%, y* {100 * (y_star / Y0 - 1):+.3f}%, "
f"w* {100 * (w_star / w0 - 1):+.3f}%, r* {100 * (r_star / r0 - 1):+.3f}%")1% higher A: Y* +1.000%, y* +1.000%, w* +1.000%, r* +1.000%
1% higher K: Y* +0.332%, y* +0.332%, w* +0.332%, r* -0.661%
1% higher L: Y* +0.666%, y* -0.331%, w* -0.331%, r* +0.666%
The answers are close to the elasticities but not exactly equal, because an elasticity describes very small changes and 1 percent is small but not zero.
Check 6: development accounting with Islandia. As in the example, Islandia has one-eighth of the benchmark’s capital per person and one-eighth of its income. The code calls the benchmark “U.S.”, as the data do; in the story it is Mainland. Equation (8) should give a TFP of \(1/4\), equation (9) should split the income gap of 8 into \(4 \times 2\), and a machine should earn the same marginal product in the two countries.
# Step 1: Islandia's capital and income per person, and the TFP that fits them
k_poor, y_poor = 1 / 8, 1 / 8 # relative to the United States = 1
predicted = k_poor ** (1 / 3) # with U.S. productivity
A_poor = y_poor / k_poor ** (1 / 3) # equation (8)
capital_factor = (1 / k_poor) ** (1 / 3)
# Step 2: split the income gap into a TFP factor and a capital factor, equation (9)
print(f"Predicted income with U.S. TFP: {predicted:.3f}; implied TFP: {A_poor:.3f}")
print(f"Income gap {1 / y_poor:.1f} = TFP factor {1 / A_poor:.1f} x capital factor {capital_factor:.1f} (equation 9)")
# Step 3: `np.log` is the natural logarithm. The shares of the log gap add up to one,
# because the log of a product is the sum of the logs.
print(f"Share of the log gap due to capital: {np.log(capital_factor) / np.log(1 / y_poor):.3f}")
# Step 4: the return on a machine, without and with Islandia's lower TFP
print(f"MPK poor / MPK U.S. with equal TFP: {mpk(k_poor, 1) / mpk(1, 1):.2f}")
print(f"MPK poor / MPK U.S. with its TFP: {mpk(k_poor, 1, A_poor) / mpk(1, 1):.2f}")Predicted income with U.S. TFP: 0.500; implied TFP: 0.250
Income gap 8.0 = TFP factor 4.0 x capital factor 2.0 (equation 9)
Share of the log gap due to capital: 0.333
MPK poor / MPK U.S. with equal TFP: 4.00
MPK poor / MPK U.S. with its TFP: 1.00
The production function and its equilibrium
We start with the shape of the production function and then solve for the prices that clear the markets for capital and labor.
Fig. 1: The diminishing marginal product of capital
Goal. See why each extra machine adds less output than the one before.
Hold the number of workers fixed at \(L\) and draw output against capital. The curve rises, but it gets flatter: going from 1 to 2 machines adds much more output than going from 7 to 8 machines. The slope of the curve is the marginal product of capital, and it falls as \(K\) rises.
First we compute the two numbers the title reports. output_gains gives the extra output from the second machine and from the eighth: the drop between them is the diminishing marginal product of capital.
def output_gains(A, L, alpha):
"""The output gains of Fig. 1: from K = 1 to 2 and from K = 7 to 8.
Equation (1), Y = A K^alpha L^(1 - alpha), at two pairs of capital stocks. One more
machine adds about the marginal product of capital, equation (2).
Arguments
A : productivity, A-bar
L : number of workers, L-bar
alpha : the exponent on capital
Returns
gain_1_2 : extra output from the 2nd machine, Y(K = 2) - Y(K = 1)
gain_7_8 : extra output from the 8th machine, Y(K = 8) - Y(K = 7)
"""
# the same L workers in both cases: only capital changes
gain_1_2 = production(2, L, A, alpha) - production(1, L, A, alpha)
gain_7_8 = production(8, L, A, alpha) - production(7, L, A, alpha)
return gain_1_2, gain_7_8Show the drawing code
# @title Drawing code: Fig. 1, the dashed guides at K = 1, 2, 7 and 8
def add_guides(fig, L, A, alpha):
"""The dashed guides of Fig. 1, from the axes to the curve at K = 1, 2, 7 and 8.
They mark the two output gains reported in the title.
Arguments
fig : the figure to draw in
L : number of workers, L-bar
A : productivity, A-bar
alpha : the exponent on capital
"""
# a `for` loop repeats the indented lines once for each value in the list
for K0 in [1, 2, 7, 8]:
Y0 = production(K0, L, A, alpha)
# a gray dashed line from (K0, 0) up to the curve and left to (0, Y0)
fig.add_trace(
go.Scatter(
x=[K0, K0, 0],
y=[0, Y0, Y0],
mode="lines",
showlegend=False,
line=dict(color=GRAY, width=1, dash="dash"),
)
)Show the drawing code
# @title Drawing code: Fig. 1, output against capital
def production_curve(A=1.0, L=64, alpha=1 / 3):
"""Output against capital, with the number of workers held fixed.
Draws Fig. 1 (adapted from Fig. 4.1 in Jones), with dots at K = 1, 2, ..., 10.
Arguments
A : productivity, A-bar
L : number of workers, L-bar
alpha : the exponent on capital
Returns
a Plotly figure
"""
# Step 1: the values of K. `np.linspace(0, 10.4, 300)` creates 300 evenly
# spaced numbers from 0 to 10.4, the points at which we draw the curve;
# `np.arange(1, 11)` gives the whole numbers 1, 2, ..., 10 (11 is left out)
K = np.linspace(0, 10.4, 300)
dots = np.arange(1, 11)
# Step 2: `go.Figure()` starts an empty chart, and each `add_trace` adds one
# line or set of dots to it: the dashed guides, the curve and the dots
fig = go.Figure()
add_guides(fig, L, A, alpha)
fig.add_trace(
go.Scatter(
x=K,
y=production(K, L, A, alpha),
name="Output, Y",
line=dict(color=GREEN, width=4),
)
)
fig.add_trace(
go.Scatter(
x=dots,
y=production(dots, L, A, alpha),
name="K = 1, 2, ..., 10",
mode="markers",
marker=dict(color="black", size=10),
)
)
# Step 3: the curve's name, written above its right end. `add_annotation`
# writes a piece of text at a point of the chart; `K[-1]` is the last value of K.
# The axis grows only if the curve ends above 54
Y_end = production(K[-1], L, A, alpha)
fig.add_annotation(
x=K[-1],
y=Y_end,
text="Y = A K<sup>α</sup> L<sup>1−α</sup>",
showarrow=False,
xanchor="right",
yanchor="bottom",
yshift=8,
)
# Step 4: the title reports the two output gains (output_gains).
# `max(60, 1.1 * Y_end)` is the larger of the two numbers: the vertical axis
# stays at 0 to 60 unless the curve would leave it
gain_1_2, gain_7_8 = output_gains(A, L, alpha)
y_top = max(60, 1.1 * Y_end)
fig.update_layout(
title=(
f"Output gain from K = 1 to 2: {gain_1_2:.2f} tons; "
f"from K = 7 to 8: {gain_7_8:.2f} tons"
),
xaxis=dict(title="Capital, K", range=[0, 10.4], tickvals=dots),
yaxis=dict(title="Tons of ice cream, Y", range=[0, y_top], dtick=10),
showlegend=False,
)
return figThe textbook does not say how many workers there are, so we pick our own round number: \(L = 64\) workers. With \(\bar{A}=1\), one machine then makes \(64^{2/3} = 16\) tons and eight machines make \(16 \cdot 8^{1/3} = 32\) tons.
For the exponent we use a SelectionSlider. It is a slider that moves through a fixed list of values, here a few simple fractions.
# The exponents of the capital slider. A dictionary such as {"1/3": 1 / 3} links a
# label (the key) to a value. `ALPHAS.items()` gives the (label, value) pairs, which
# is what `SelectionSlider` needs: it shows the label and passes the value on
ALPHAS = {
"1/4": 1 / 4,
"1/3": 1 / 3,
"1/2": 1 / 2,
"2/3": 2 / 3,
"3/4": 3 / 4,
"1": 1.0,
}Now the interactive version. Move the sliders and watch the two readouts in the title.
# Fig. 1 with sliders. `interact` turns each argument of production_curve into a
# slider, and draws the figure again every time you move one. The `;` at the end
# only hides an extra line of text.
interact(
production_curve,
A=FloatSlider(value=1.0, min=0.5, max=1.5, step=0.1,
description="productivity A", style=WIDE),
L=FloatSlider(value=64, min=8, max=120, step=8,
description="workers L", style=WIDE),
alpha=SelectionSlider(options=list(ALPHAS.items()), value=1 / 3,
description="capital exponent α", style=WIDE),
);Show the code of the website version
# @title Website version: Fig. 1 with a precomputed slider for the number of workers
# one picture of Fig. 1 per value of L; the slider shows one at a time
slider_figure(
production_curve,
"L",
[8, 16, 32, 48, 64, 80, 96, 112], # the values of L
start=64,
label="workers L",
)Source. Adapted from Fig. 4.1 in Jones (2024), Macroeconomics, 6th ed.
What to notice.
- The dots get closer together vertically as \(K\) rises: each extra machine adds less output. With the defaults, the first extra machine adds 4.16 tons and the eighth adds only 1.39 tons.
- More workers lift the whole curve. Each machine is more useful when there are more people to run it.
- Set \(\alpha = 1\): the curve becomes a straight line and the marginal product no longer falls. With \(\alpha = 1/4\) the curve bends even more sharply.
Your turn. With the default settings, by how much does output rise when capital goes from 9 to 10? Compute it in the cell below with the production function. Is it more or less than 1.39 tons?
Hint
Type production(10, 64) - production(9, 64). The arguments A and alpha keep their default values.
# Your turn: try it hereFig. 2: Supply and demand for capital and labor
Goal. Solve the production model and find the equilibrium prices of capital and labor.
Firms rent capital until its marginal product equals the rental price \(r\), and hire workers until their marginal product equals the wage \(w\). So the MPK and MPL curves are the demand curves for capital and labor. The supplies are fixed at \(\bar{K}\) and \(\bar{L}\), so the supply curves are vertical lines. Where supply meets demand, the markets clear (equation 4):
\[r^* = \frac{1}{3}\,\bar{A}\left(\frac{\bar{L}}{\bar{K}}\right)^{2/3} \qquad\qquad w^* = \frac{2}{3}\,\bar{A}\left(\frac{\bar{K}}{\bar{L}}\right)^{1/3}\]
Both panels have the same parts, so one function draws a panel and we call it twice.
First the economics. factor_prices finds the prices that clear the two markets, equation (4), and the shares of output they pay to capital and to labor, equation (5).
def factor_prices(K_bar, L_bar, A, alpha):
"""The equilibrium of Fig. 2: the prices of capital and labor, and their shares.
Equation (4): r* = MPK and w* = MPL at the supplies K-bar and L-bar.
Equation (5): capital gets r* K-bar / Y* of output, and labor w* L-bar / Y*.
Arguments
K_bar : supply of capital, K-bar
L_bar : supply of labor, L-bar
A : productivity, A-bar
alpha : the exponent on capital
Returns
r_star, w_star : the rental price of capital and the real wage
capital_share, labor_share : the shares of output paid to capital and labor
"""
# Step 1: each market clears where demand (the marginal product) meets supply
r_star, w_star = mpk(K_bar, L_bar, A, alpha), mpl(K_bar, L_bar, A, alpha)
Y_star = production(K_bar, L_bar, A, alpha)
# Step 2: the payments to each factor, as a share of output
capital_share = r_star * K_bar / Y_star
labor_share = w_star * L_bar / Y_star
return r_star, w_star, capital_share, labor_shareShow the drawing code
# @title Drawing code: Fig. 2, the names of the curves in one panel
def label_market(fig, col, x, demand, supply, top, names):
"""The names of the demand curve and the supply line in one panel of Fig. 2.
Arguments
fig : the two-panel figure
col : the panel, 1 (capital) or 2 (labor)
x : the quantities of the demand curve
demand : the demand curve, the marginal product at each x
supply : the fixed supply, K-bar or L-bar
top : the top of the vertical axis
names : the two names, of the demand curve and of the supply line
"""
# `demand <= 0.65 * top` is True where the demand curve is in the lower part of
# the panel; `x[low]` keeps those values of x, and `[0]` takes the first of
# them: there we write the curve's name, below the name of the supply line
low = demand <= 0.65 * top
# `names[0]` is the first item of the list `names`
fig.add_annotation(
x=x[low][0],
y=demand[low][0],
text=names[0],
showarrow=False,
yanchor="bottom",
yshift=6,
row=1,
col=col,
)
# the supply line's name, above its top end
fig.add_annotation(
x=supply,
y=0.8 * top,
text=names[1],
showarrow=False,
yanchor="bottom",
row=1,
col=col,
)Show the drawing code
# @title Drawing code: Fig. 2, one market panel
def add_market(fig, col, x, demand, supply, price, top, names):
"""One panel of Fig. 2: the demand curve, the supply line and the equilibrium.
Arguments
fig : the two-panel figure
col : the panel, 1 (capital) or 2 (labor)
x : the quantities of the demand curve
demand : the demand curve, the marginal product at each x
supply : the fixed supply, K-bar or L-bar
price : the equilibrium price, r* or w*
top : the top of the vertical axis
names : the two names, of the demand curve and of the supply line
"""
# Step 1: `a if condition else b` gives a when the condition is true and b
# otherwise: solid lines in the first panel, dotted ones in the second
dash = "solid" if col == 1 else "dot"
# Step 2: the demand curve and the vertical supply line. `row=..., col=...`
# tells `add_trace` which panel to draw in
demand_curve = go.Scatter(
x=x,
y=demand,
name="Demand",
line=dict(color=BLUE, width=4, dash=dash),
)
fig.add_trace(demand_curve, row=1, col=col)
supply_line = go.Scatter(
x=[supply, supply],
y=[0.05, 0.8 * top],
name="Supply",
mode="lines",
line=dict(color=TEAL, width=4, dash=dash),
)
fig.add_trace(supply_line, row=1, col=col)
# Step 3: the equilibrium: a dashed guide to the price axis, and a black dot
guide = go.Scatter(
x=[0, supply],
y=[price, price],
mode="lines",
line=dict(color=GRAY, dash="dash"),
)
fig.add_trace(guide, row=1, col=col)
dot = go.Scatter(
x=[supply],
y=[price],
name="Equilibrium",
mode="markers",
marker=dict(color="black", size=12),
)
fig.add_trace(dot, row=1, col=col)
# Step 4: the names of the curves (label_market), and the panel's vertical axis
label_market(fig, col, x, demand, supply, top, names)
fig.update_yaxes(range=[0, top], row=1, col=col)Show the drawing code
# @title Drawing code: Fig. 2, the capital market and the labor market
def factor_markets(K_bar=1.0, L_bar=1.0, A=1.0, alpha=1 / 3):
"""Supply and demand in (a) the capital market and (b) the labor market.
Draws Fig. 2 (adapted from Fig. 4.2 in Jones), with the curve names on the plot.
Arguments
K_bar : supply of capital, K-bar
L_bar : supply of labor, L-bar
A : productivity, A-bar
alpha : the exponent on capital
Returns
a Plotly figure
"""
# Step 1: the capital and labor demanded, for the demand curves, and the
# equilibrium (factor_prices)
K = L = np.linspace(0.3, 2.2, 200)
r_star, w_star, capital_share, labor_share = factor_prices(K_bar, L_bar, A, alpha)
# Step 2: `make_subplots(rows=1, cols=2)` creates a figure with two panels
# side by side
fig = make_subplots(
rows=1,
cols=2,
subplot_titles=("(a) The capital market", "(b) The labor market"),
)
# Step 3: the two markets (add_market). The vertical axes are fixed, and grow
# only when an equilibrium price is high
add_market(
fig, 1, K, mpk(K, L_bar, A, alpha), K_bar, r_star, max(1.15, 2.2 * r_star),
["Demand<br>MPK", "Supply of<br>capital"],
)
add_market(
fig, 2, L, mpl(K_bar, L, A, alpha), L_bar, w_star, max(1.7, 2.2 * w_star),
["Demand<br>MPL", "Supply of<br>labor"],
)
# Step 4: the axes of the two panels
fig.update_xaxes(
title_text="Capital, K",
range=[0, 2.2],
tickvals=[0.5, 1, 1.5, 2],
row=1,
col=1,
)
fig.update_yaxes(title_text="Rental price, r", dtick=0.2, row=1, col=1)
fig.update_xaxes(
title_text="Labor, L",
range=[0, 2.2],
tickvals=[0.5, 1, 1.5, 2],
row=1,
col=2,
)
fig.update_yaxes(title_text="Real wage, w", dtick=0.5, row=1, col=2)
# Step 5: the title reports both prices and the two shares of output
fig.update_layout(
title=(
f"r* = {r_star:.3f} w* = {w_star:.3f} | "
f"capital gets {capital_share:.0%} of output, labor {labor_share:.0%}"
),
showlegend=False,
)
return fig# Fig. 2 with sliders for the two supplies, productivity and the exponent
interact(
factor_markets,
K_bar=FloatSlider(value=1.0, min=0.5, max=2.0, step=0.1,
description="supply of capital K̄", style=WIDE),
L_bar=FloatSlider(value=1.0, min=0.5, max=2.0, step=0.1,
description="supply of labor L̄", style=WIDE),
A=FloatSlider(value=1.0, min=0.5, max=1.5, step=0.1,
description="productivity A", style=WIDE),
alpha=SelectionSlider(options=list(ALPHAS.items()), value=1 / 3,
description="capital exponent α", style=WIDE),
);Show the code of the website version
# @title Website version: Fig. 2 with a precomputed slider for the supply of capital
# one picture of Fig. 2 per value of K-bar
slider_figure(
factor_markets,
"K_bar",
[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], # the values of K-bar
start=1.0,
label="supply of capital K̄",
)Source. Adapted from Fig. 4.2 in Jones (2024), Macroeconomics, 6th ed.
What to notice.
- With \(\bar{K} = \bar{L} = \bar{A} = 1\), the rental price is \(r^* = 1/3\) and the wage is \(w^* = 2/3\).
- More capital lowers its rental price, because the marginal product of capital falls. It also raises the wage: each worker now has more machines to work with, so the labor demand curve shifts up.
- Whatever you do to \(\bar{K}\), \(\bar{L}\) or \(\bar{A}\), capital always gets the same share of output and labor gets the rest (equation 5). Only the exponent \(\alpha\) changes the split.
Your turn. Use the sliders to find what happens to the wage \(w^*\) when the number of workers \(\bar{L}\) doubles from 1 to 2, holding \(\bar{K}\) fixed. Then check your answer with the mpl function.
Hint
\(w^* = \frac{2}{3}\bar{A}(\bar{K}/\bar{L})^{1/3}\), so doubling \(\bar{L}\) multiplies the wage by \((1/2)^{1/3}\). In code: mpl(1, 2) / mpl(1, 1).
# Your turn: try it hereCapital income and the stock market
Owners of capital receive part of what the economy produces. The stock market puts a price on that income, so a long series of U.S. share prices lets us see it in the data.
Fig. 3: The S&P 500 stock price index
Goal. Read a long financial time series and connect it to the capital income of the model.
In the model, capital earns \(r^*K^*\), one-third of output. A company’s stock market value is the value of its current and future capital income, so a stock price index measures the value of (part of) the economy’s capital stock. The S&P 500 covers 500 large U.S. companies. Dividing it by the consumer price index gives a real index, in dollars of the same purchasing power.
# Load the S&P 500 data of Fig. 3. `pd.read_csv` reads a data file into a
# DataFrame, a table with named columns; `parse_dates=["date"]` tells pandas that
# the `date` column holds dates, not plain text
stocks = pd.read_csv(DATA_URL + "fig4-3.csv", parse_dates=["date"])
# `.dt.year` extracts the year of each date; we store it as a new column
stocks["year"] = stocks.date.dt.year
# `.tail()` shows the last five rows
stocks.tail()| date | sp500_real | year | |
|---|---|---|---|
| 1275 | 2026-05-01 | 7452.74 | 2026 |
| 1276 | 2026-06-01 | 7516.69 | 2026 |
| 1277 | 2026-07-01 | 7549.05 | 2026 |
| 1278 | 2026-08-01 | 7756.44 | 2026 |
| 1279 | 2026-09-01 | 7669.41 | 2026 |
The index is in September 2026 dollars, so it is about 10 percent higher than in the textbook, which uses 2023 dollars.
The title of the chart reports two numbers for the chosen period: by what factor the real index changed, and its average growth a year. stock_growth computes both.
def stock_growth(period):
"""The real S&P 500 over a period, and how much it grew (Fig. 3).
Average annual growth, in percent: 100 ((last / first)^(1 / years) - 1)
Arguments
period : (start, end), the first and the last year
Returns
chosen : the rows of `stocks` from start to end
factor : the last value of the real index divided by the first
growth : the average annual growth of the real index, in percent
"""
# Step 1: `start, end = period` unpacks the pair of years into two variables.
# `stocks[condition]` keeps only the rows where the condition is true, and
# `&` means "and"
start, end = period
chosen = stocks[(stocks.year >= start) & (stocks.year <= end)]
# Step 2: the first and the last value, and the years between them
first, last = chosen.sp500_real.iloc[0], chosen.sp500_real.iloc[-1]
years = (chosen.date.iloc[-1] - chosen.date.iloc[0]).days / 365.25
# Step 3: the factor, and the average annual growth in percent
factor = last / first
growth = 100 * ((last / first) ** (1 / years) - 1)
return chosen, factor, growthShow the drawing code
# @title Drawing code: Fig. 3, the real S&P 500 index
def stock_chart(period=(1920, 2026), scale="ratio"):
"""The real S&P 500 index over a chosen period, on a ratio or linear scale.
Draws Fig. 3 (inspired by Fig. 4.3 in Jones).
Arguments
period : (start, end), the first and the last year
scale : "ratio" or "linear", the vertical scale
Returns
a Plotly figure
"""
# Step 1: the data and the two numbers of the title (stock_growth)
start, end = period
chosen, factor, growth = stock_growth(period)
# Step 2: `px.area` draws a line with the area below it shaded; `log_y=True`
# gives a ratio scale (here: when the scale is "ratio")
fig = px.area(
chosen,
x="date",
y="sp500_real",
log_y=(scale == "ratio"),
labels={"date": "Year", "sp500_real": "Real index (Sep 2026 dollars)"},
title=(
f"{start}–{end}: the real index changed by a factor of {factor:.1f}, "
f"or {growth:+.1f}% a year"
),
)
# Step 3: the book's blue, with a light shade of it below the line
fig.update_traces(line_color=BLUE, fillcolor="rgba(22, 82, 140, 0.15)")
# a tick every 10 years (120 months), none in the corner
fig.update_xaxes(dtick="M120", tick0="1930-01-01")
if scale == "ratio":
# each tick doubles the one below it
fig.update_yaxes(tickvals=[80, 160, 320, 640, 1280, 2560, 5120, 10240])
return fig# Fig. 3 with a period slider and scale buttons. An `IntRangeSlider` has two
# handles and gives back a tuple, a pair of values written (1920, 2026). A
# `ToggleButtons` widget shows one button per option.
interact(
stock_chart,
period=IntRangeSlider(value=(1920, 2026), min=1920, max=2026, step=1,
description="period", style=WIDE),
scale=ToggleButtons(options=["ratio", "linear"], description="vertical scale"),
);Show the code of the website version
# @title Website version: Fig. 3 with buttons for the vertical scale and a range slider
# Step 1: the default chart, with a small range slider under the axis to zoom in
fig = stock_chart()
fig.update_xaxes(rangeslider_visible=True)
# Step 2: two buttons switch the vertical axis between a ratio and a linear scale
fig.update_layout(
updatemenus=[
dict(
type="buttons",
direction="right",
x=0.01,
y=0.98,
xanchor="left",
yanchor="top",
buttons=[
dict(
label="ratio scale",
method="relayout",
args=[{
"yaxis.type": "log",
"yaxis.autorange": True,
"yaxis.tickvals": [80, 160, 320, 640, 1280, 2560, 5120, 10240],
}],
),
dict(
label="linear scale",
method="relayout",
args=[{
"yaxis.type": "linear",
"yaxis.autorange": True,
"yaxis.tickvals": None,
}],
),
],
)
]
)
figSources. Data: Robert Shiller, U.S. stock market data (S&P composite index), read from the datahub.io copy up to August 2023 and extended with S&P Dow Jones Indices, S&P 500 (SP500), via FRED; deflated by the U.S. Bureau of Labor Statistics, Consumer Price Index for All Urban Consumers (CPIAUCNS), via FRED. Prepared file: data/ch04/fig4-3.csv (see the data notes). Chart: inspired by Fig. 4.3 in Jones (2024), Macroeconomics, 6th ed.
What to notice.
- On the ratio scale, equal vertical distances mean equal percentage changes, and every tick doubles the one below it. The big swings stand out: the 1929 peak and the 1932 crash, the long fall from the late 1960s to 1982, the 2000 peak and the 2009 trough.
- Switch to the linear scale. The early decades flatten into a line near zero, and only the recent swings look large. For a series that grows a lot, the ratio scale is the honest picture.
- Over the whole period, the real index has grown by a factor of about 50.
Comparing the model with the data
If countries differed only in capital per person, how rich would each one be? These figures put the model’s predictions next to the actual incomes.
Fig. 4: Predicted per capita GDP in the production model
Goal. Use the model to predict how rich each country should be, given its capital per person.
Dividing the production function, equation (1), by \(L\) gives output per person in terms of capital per person, \(k = K/L\) (equation 7):
\[y^* = \bar{A}\,\bar{k}^{1/3}\]
For now, assume every country has the same productivity, \(\bar{A} = 1\). Then the model predicts that income depends only on capital per person: \(y^* = \bar{k}^{1/3}\). To compare countries, we divide both variables by their U.S. values, so the United States equals 1.
We load the wider data file of this chapter. It has one row per country and year from the Penn World Table 11.0, with capital per person built from each country’s past investment.
# Load the wider data file: one row per country and year. `pwt.shape` gives its
# number of rows and columns (and `pwt.columns` would list the columns). The
# columns ending in `_rel` are relative to the United States in the same year.
pwt = pd.read_csv(DATA_URL + "pwt_production.csv")
print(pwt.shape)
# the first five countries in 2023
pwt[pwt.year == 2023].head()(11201, 9)
| iso3 | country | year | gdp_pc | k_pc | y_rel | k_rel | tfp | in_sample | |
|---|---|---|---|---|---|---|---|---|---|
| 53 | ABW | Aruba | 2023 | 45325.6 | 235121.0 | 0.62186 | 1.01850 | 0.61807 | False |
| 107 | AGO | Angola | 2023 | 6181.4 | 19268.0 | 0.08481 | 0.08347 | 0.19406 | False |
| 161 | AIA | Anguilla | 2023 | 22720.6 | 214528.0 | 0.31172 | 0.92929 | 0.31944 | False |
| 215 | ALB | Albania | 2023 | 15969.0 | 44341.0 | 0.21909 | 0.19208 | 0.37973 | False |
| 269 | ARE | United Arab Emirates | 2023 | 76379.2 | 329455.0 | 1.04791 | 1.42713 | 0.93076 | False |
The function below picks the eight countries of the textbook figure, or any others you choose, in a given year and places them on the curve. All the other countries of the textbook’s sample are one click away: click “Other countries” in the legend to show them as gray dots.
# The eight countries of the textbook figure
EIGHT = ["United States", "Switzerland", "Japan", "Italy",
"United Kingdom", "China", "Brazil", "Burundi"]
def predicted_incomes(year, countries):
"""The model's predicted incomes of Fig. 4, y* = k^(1/3), with U.S. = 1.
Equation (7) with the same productivity everywhere, A-bar = 1.
Arguments
year : the year of the data
countries : the chosen countries
Returns
chosen : the chosen countries, with their predicted income in `predicted`
rest : the other countries of the textbook's sample
"""
# Step 1: the textbook's sample in that year
in_year = pwt[(pwt.year == year) & pwt.in_sample]
# Step 2: the chosen countries. `.isin(countries)` is True for the rows whose
# country is in the list; `.copy()` makes a separate table, so that we can add
# a column to it safely
chosen = in_year[in_year.country.isin(countries)].copy()
chosen["predicted"] = chosen.k_rel ** (1 / 3) # equation (7), A-bar = 1
# Step 3: the other countries; `~` means "not"
rest = in_year[~in_year.country.isin(countries)]
return chosen, restShow the drawing code
# @title Drawing code: Fig. 4, the other countries and the curve
# short labels, and where to put the labels that would overlap
SHORT = {"United States": "U.S.", "United Kingdom": "U.K."}
POSITIONS = {
"Japan": "bottom right",
"Italy": "bottom right",
"Brazil": "bottom right",
"Burundi": "middle right",
}
def add_rest_and_curve(fig, rest):
"""The gray dots of the other countries and the curve y* = k^(1/3) (Fig. 4).
Arguments
fig : the figure to draw in
rest : the other countries of the sample (predicted_incomes)
"""
# `visible="legendonly"` hides the gray dots until you click their name in
# the legend
fig.add_trace(
go.Scatter(
x=rest.k_rel,
y=rest.k_rel ** (1 / 3),
mode="markers",
name="Other countries",
text=rest.country,
hoverinfo="text",
visible="legendonly",
marker=dict(color=GRAY, size=8),
)
)
# the model's curve, in blue
k = np.linspace(0, 2, 300)
fig.add_trace(
go.Scatter(
x=k,
y=k ** (1 / 3),
name="y* = k^(1/3)",
mode="lines",
line=dict(color=BLUE, width=3),
)
)Show the drawing code
# @title Drawing code: Fig. 4, predicted income against capital per person
def predicted_income_chart(year=2023, countries=tuple(EIGHT)):
"""Predicted income y* = k^(1/3) for the chosen countries, with U.S. = 1.
Draws Fig. 4 (inspired by Fig. 4.4 in Jones).
Arguments
year : the year of the data
countries : the countries drawn as black dots
Returns
a Plotly figure
"""
# Step 1: the predictions (predicted_incomes). `.replace(SHORT)` swaps long
# names for the short ones; `.map(POSITIONS)` looks up each country in
# POSITIONS, and `.fillna("top left")` fills in the countries that are not in it
chosen, rest = predicted_incomes(year, countries)
chosen["label"] = chosen.country.replace(SHORT)
chosen["position"] = chosen.country.map(POSITIONS).fillna("top left")
# Step 2: the chosen countries as black dots with their labels
fig = px.scatter(
chosen,
x="k_rel",
y="predicted",
text="label",
hover_name="country",
labels={
"k_rel": "Capital per person, k (U.S. = 1)",
"predicted": "Predicted income, y* (U.S. = 1)",
},
)
fig.update_traces(
textposition=list(chosen.position),
marker=dict(color="black", size=11),
name="Chosen countries",
)
# Step 3: the other countries and the curve (add_rest_and_curve)
add_rest_and_curve(fig, rest)
# Step 4: the title reports China, or else the first chosen country
others = chosen[chosen.country != "United States"]
title = f"{year}: choose a country to compare with the United States"
if len(others) > 0:
if "China" in set(others.country):
focus = others[others.country == "China"].iloc[0]
else:
focus = others.iloc[0]
title = (
f"{year}: {short(focus.country)} has {focus.k_rel:.0%} of U.S. capital "
f"per person, predicted income {focus.predicted:.0%}"
)
fig.update_layout(
title=title,
xaxis_range=[0, 2],
yaxis=dict(range=[0, 1.3], tickvals=[0.2, 0.4, 0.6, 0.8, 1.0, 1.2]),
)
return fig# Fig. 4 with a menu of years and a list of countries. A `Dropdown` widget shows a
# menu; a `SelectMultiple` a list (hold Ctrl or Cmd to pick several)
interact(
predicted_income_chart,
year=Dropdown(options=[1980, 1990, 2000, 2010, 2019, 2023], value=2023,
description="year", style=WIDE),
countries=SelectMultiple(
options=sorted(pwt[pwt.in_sample & (pwt.year == 2023)].country),
value=tuple(EIGHT),
rows=8,
description="countries",
style=WIDE,
),
);Show the code of the website version
# @title Website version: Fig. 4 with a precomputed slider for the year
# Step 1: one picture of Fig. 4 per year
fig = slider_figure(
predicted_income_chart,
"year",
[1980, 1990, 2000, 2010, 2019, 2023],
start=2023,
label="year",
)
# Step 2: "Other countries" stays hidden until it is clicked in the legend
others = [trace.name == "Other countries" for trace in fig.data]
for trace, other in zip(fig.data, others):
if other and trace.visible:
trace.visible = "legendonly" # hidden until clicked in the legend
for step in fig.layout.sliders[0].steps: # ... in every year of the slider
visible = step.args[0]["visible"]
step.args = [
{
"visible": [
"legendonly" if v and other else v
for v, other in zip(visible, others)
]
},
step.args[1],
]
figSources. Data: Penn World Table 11.0, Groningen Growth and Development Centre (DOI 10.34894/FABVLR), variables rgdpo (real GDP), pop (population) and csh_i (investment share); cite as Feenstra, Inklaar and Timmer (2015), “The Next Generation of the Penn World Table”, American Economic Review 105(10). Capital per person is our own perpetual-inventory estimate from PWT investment, and TFP is computed from it. The curve is the model’s prediction, \(y^* = k^{1/3}\). Prepared file: data/ch04/pwt_production.csv (see the data notes). Chart: inspired by Fig. 4.4 in Jones (2024), Macroeconomics, 6th ed.
On the website: click “Other countries” in the legend to show the rest of the textbook’s sample.
What to notice.
- Capital per person differs enormously: Burundi has less than 1 percent of the U.S. level, and Switzerland about 60 percent more. The predicted incomes differ much less, because the curve is so bent. This is diminishing returns to capital at work.
- Switzerland’s capital per person is more than double the United Kingdom’s, yet the model predicts it to be only about 30 percent richer.
- Choose earlier years: China moves up along the curve as it accumulates capital, from about 3 percent of U.S. capital per person in 1980 to 35 percent in 2023.
- Add South Korea and India. In 2023 Korea has about as much capital per person as the United States, a little more than Japan. India has about 9 percent, so the model predicts India’s income at about 45 percent of the U.S. level.
Table 4.3: the model’s prediction for per capita GDP
The textbook lists the numbers behind the figure for 11 countries in 2019. We rebuild the table from our data and put the textbook’s values next to it. Our data come from a newer release of the Penn World Table, so the numbers differ a little, but the pattern is the same.
# Table 4.3: the textbook's 11 countries in 2019, next to the textbook's values.
# `.set_index("country")` uses the country names as row labels, and `.loc[ELEVEN]`
# picks the rows in the order of the list
ELEVEN = ["United States", "Switzerland", "Japan", "Italy", "Spain", "United Kingdom",
"Brazil", "China", "South Africa", "India", "Burundi"]
y2019 = pwt[pwt.year == 2019].set_index("country").loc[ELEVEN]
# `pd.DataFrame({...})` builds a table from a dictionary: each key becomes a column
table_4_3 = pd.DataFrame({
"capital per person k": y2019.k_rel,
"predicted y* = k^(1/3)": y2019.k_rel ** (1 / 3), # equation (7), A-bar = 1
"observed y": y2019.y_rel,
"textbook: k": [1.000, 1.488, 0.914, 0.792, 0.716, 0.714,
0.195, 0.275, 0.165, 0.082, 0.005],
"textbook: observed y": [1.000, 1.203, 0.634, 0.651, 0.645, 0.707,
0.233, 0.226, 0.200, 0.107, 0.013],
})
table_4_3.round(3) # rounded to three decimals| capital per person k | predicted y* = k^(1/3) | observed y | textbook: k | textbook: observed y | |
|---|---|---|---|---|---|
| country | |||||
| United States | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 |
| Switzerland | 1.557 | 1.159 | 1.252 | 1.488 | 1.203 |
| Japan | 0.985 | 0.995 | 0.645 | 0.914 | 0.634 |
| Italy | 0.825 | 0.938 | 0.693 | 0.792 | 0.651 |
| Spain | 0.728 | 0.899 | 0.666 | 0.716 | 0.645 |
| United Kingdom | 0.734 | 0.902 | 0.738 | 0.714 | 0.707 |
| Brazil | 0.203 | 0.588 | 0.253 | 0.195 | 0.233 |
| China | 0.287 | 0.659 | 0.265 | 0.275 | 0.226 |
| South Africa | 0.168 | 0.552 | 0.210 | 0.165 | 0.200 |
| India | 0.084 | 0.437 | 0.111 | 0.082 | 0.107 |
| Burundi | 0.005 | 0.169 | 0.015 | 0.005 | 0.013 |
Compare the second and third columns: for almost every country the model predicts a higher income than the country actually has. Italy, for example, has about 82 percent of U.S. capital per person, so the model predicts 94 percent of U.S. income, but Italy’s actual income is only 69 percent.
Your turn. Add a column to table_4_3 with the ratio of predicted to observed income. Which country does the model miss by the largest factor?
Hint
table_4_3["ratio"] = table_4_3["predicted y* = k^(1/3)"] / table_4_3["observed y"], then sort the table with .sort_values("ratio").
# Your turn: try it hereFig. 5: The model’s prediction for per capita GDP
Goal. Compare the model’s predictions with the data for 111 countries.
We plot actual income on the horizontal axis and the model’s prediction on the vertical axis. If the model were right, every country would lie on the 45-degree line, where prediction equals data.
First, a quick look at the data. We keep the 111 countries that have data since 1960, the textbook’s sample, and sort them from poorest to richest.
# The textbook's sample in 2023: the countries with data since 1960. Then the
# poorest five: `.sort_values("y_rel")` sorts by income relative to the U.S.
sample = pwt[(pwt.year == 2023) & pwt.in_sample]
sample.sort_values("y_rel").head()| iso3 | country | year | gdp_pc | k_pc | y_rel | k_rel | tfp | in_sample | |
|---|---|---|---|---|---|---|---|---|---|
| 10844 | VEN | Venezuela (Bolivarian Republic of) | 2023 | 159.1 | 35653.0 | 0.00218 | 0.15444 | 0.00407 | True |
| 677 | BDI | Burundi | 2023 | 990.8 | 1043.0 | 0.01359 | 0.00452 | 0.08221 | True |
| 2227 | COD | D.R. of the Congo | 2023 | 1072.1 | 2138.0 | 0.01471 | 0.00926 | 0.07005 | True |
| 1732 | CAF | Central African Republic | 2023 | 1098.4 | 2706.0 | 0.01507 | 0.01172 | 0.06634 | True |
| 7111 | MOZ | Mozambique | 2023 | 1542.9 | 5109.0 | 0.02117 | 0.02213 | 0.07540 | True |
Look at the first row. Venezuela’s GDP per person appears to be about 0.2 percent of the U.S. level, or roughly 160 dollars a year, which is not plausible. During Venezuela’s hyperinflation the price data behind this number broke down. Always look at your data before using it. We drop Venezuela and continue with 110 countries.
# Drop Venezuela, whose income data broke down; `len(sample)` counts the rows left
sample = sample[sample.country != "Venezuela (Bolivarian Republic of)"]
len(sample)110
Both axes use a ratio scale, labeled with fractions of the U.S. level. We also let you change the exponent \(\alpha\) on capital, to test the textbook’s argument about it.
The title reports two numbers for each exponent: the share of countries that lie above the 45-degree line, and how many times richer the model predicts the typical country to be. prediction_gap computes them.
def prediction_gap(alpha):
"""The model's predictions of Fig. 5 against the data, y* = k^alpha (U.S. = 1).
Arguments
alpha : the exponent on capital
Returns
chosen : the sample, with the predicted income in the column `predicted`
above : the share of countries above the 45-degree line (predicted > actual)
gap : the median of predicted over actual income: how far off the
typical country is
"""
# Step 1: the prediction for every country of the sample
chosen = sample.copy()
chosen["predicted"] = chosen.k_rel ** alpha
# Step 2: the mean of a True/False column is the share of True values
above = (chosen.predicted > chosen.y_rel).mean()
gap = (chosen.predicted / chosen.y_rel).median()
return chosen, above, gapShow the drawing code
# @title Drawing code: Figs. 5 and 7, the ticks of the ratio scales
# the ticks, as fractions of the U.S. level, and the text written at each
FRACTIONS = [1 / 256, 1 / 128, 1 / 64, 1 / 32, 1 / 16, 1 / 8, 1 / 4, 1 / 2, 1, 2]
FRACTION_NAMES = ["1/256", "1/128", "1/64", "1/32", "1/16",
"1/8", "1/4", "1/2", "1", "2"]Show the drawing code
# @title Drawing code: Fig. 5, predicted against actual income
def prediction_chart(alpha=1 / 3):
"""Predicted income k^alpha against actual income, with the 45-degree line.
Draws Fig. 5 (inspired by Fig. 4.5 in Jones); both incomes relative to the U.S.
Arguments
alpha : the exponent on capital
Returns
a Plotly figure
"""
# Step 1: the predictions and the two numbers of the title (prediction_gap)
chosen, above, gap = prediction_gap(alpha)
# Step 2: one green point per country; `log_x=True` and `log_y=True` put both
# axes on a ratio scale
fig = px.scatter(
chosen,
x="y_rel",
y="predicted",
hover_name="country",
log_x=True,
log_y=True,
labels={
"y_rel": "Per capita GDP in 2023 (U.S. = 1, ratio scale)",
"predicted": "Predicted, y* = k^α (ratio scale)",
},
)
fig.update_traces(marker=dict(color=GREEN, size=9))
# Step 3: the 45-degree line, where the model equals the data
fig.add_trace(
go.Scatter(
x=[1 / 256, 2],
y=[1 / 256, 2],
name="45-degree line: model = data",
mode="lines",
line=dict(color=BLUE, width=3),
)
)
# Step 4: on a log axis Plotly expects the `range` in powers of ten, so we
# pass `np.log10(1 / 110)` rather than 1 / 110. `tickvals` says where to put
# the ticks, and `ticktext` what to write there
fig.update_xaxes(
range=[np.log10(1 / 110), np.log10(2.5)],
tickvals=FRACTIONS,
ticktext=FRACTION_NAMES,
)
fig.update_yaxes(
range=[np.log10(1 / 256), np.log10(2.5)],
tickvals=FRACTIONS,
ticktext=FRACTION_NAMES,
)
# Step 5: the title reports the share above the line and the typical gap
fig.update_layout(
title=(
f"α = {alpha:.2f}: {above:.0%} of countries lie above the line; "
f"typical gap: {gap:.1f} times"
)
)
return fig# Fig. 5 with a slider for the capital exponent
interact(
prediction_chart,
alpha=SelectionSlider(options=list(ALPHAS.items()), value=1 / 3,
description="capital exponent α", style=WIDE),
);Show the code of the website version
# @title Website version: Fig. 5 with a precomputed slider for the capital exponent
# Step 1: one picture of Fig. 5 per exponent of ALPHAS
fig = slider_figure(
prediction_chart,
"alpha",
list(ALPHAS.values()),
start=1 / 3,
label="capital exponent α",
)
# Step 2: label each step with its fraction; `zip` pairs each step with a key of ALPHAS
for step, name in zip(fig.layout.sliders[0].steps, ALPHAS):
step.label = name # show 1/3 rather than 0.333...
figSources. Data: Penn World Table 11.0, Groningen Growth and Development Centre (DOI 10.34894/FABVLR), variables rgdpo (real GDP), pop (population) and csh_i (investment share); cite as Feenstra, Inklaar and Timmer (2015), “The Next Generation of the Penn World Table”, American Economic Review 105(10). Capital per person is our own perpetual-inventory estimate from PWT investment, and TFP is computed from it. Prepared file: data/ch04/pwt_production.csv (see the data notes). Chart: inspired by Fig. 4.5 in Jones (2024), Macroeconomics, 6th ed.
What to notice.
- With \(\alpha = 1/3\), almost every country lies far above the 45-degree line: the model predicts that poor countries should be much richer than they are. Burundi is predicted to have about 1/6 of U.S. income, but it actually has about 1/74. The “typical gap” in the title is the median of predicted over actual income: the typical country is predicted to be 2.8 times richer than it is.
- The gap shrinks as income rises, and the richest countries lie close to the line.
- Move the exponent to \(\alpha = 1\), so that \(y = k\). The poor countries now overshoot and fall below the line: Burundi is predicted at about 1/221 of U.S. income, but it has about 1/74. Most rich countries stay above it. Capital’s share of income is close to 1/3 in the data, so raising the exponent is not a good way to rescue the model.
Your turn. Hover over the points to find two countries that lie unusually far above the cloud for their income level. Then find two that lie close to the 45-degree line with \(\alpha = 1/3\).
Hint
Points high above their neighbors have a lot of capital for their income. Points near the line are countries where capital alone almost explains income. Start at the top right of the chart.
# Your turn: try it hereTotal factor productivity
The model fits much better once productivity can differ across countries. Here we measure that productivity, called TFP, as the part of income that capital does not explain.
Fig. 6: The U.S. and Chinese production functions
Goal. Bring productivity back into the model and measure it.
Countries are poorer than their capital predicts, so they must use their capital and labor less efficiently: they have a lower \(\bar{A}\). We cannot observe \(\bar{A}\) directly, but we observe everything else in equation (7), \(y^* = \bar{A}\bar{k}^{1/3}\). So we choose the \(\bar{A}\) that makes the equation hold exactly (equation 8):
\[\bar{A} = \frac{y^*}{\bar{k}^{1/3}}\]
This is total factor productivity (TFP). It is already in our data as the column tfp. A lower TFP shifts the whole production function down.
First the numbers of the figure. tfp_comparison reads a country’s capital, income and implied TFP in 2023, and computes the income it would have with U.S. TFP.
def tfp_comparison(country):
"""The numbers of Fig. 6 for one country in 2023, with U.S. = 1.
Equation (8): A-bar = y* / k^(1/3). With U.S. TFP (A-bar = 1), equation (7) gives
income k^(1/3).
Arguments
country : the country compared with the United States
Returns
A : its implied TFP, A-bar
k, y : its capital and income per person
y_us_tfp : the income it would have with U.S. TFP, k^(1/3)
"""
# the country's row; `.iloc[0]` takes the first (only) row
row = sample[sample.country == country].iloc[0]
A, k, y = row.tfp, row.k_rel, row.y_rel
# equation (7) with A-bar = 1
y_us_tfp = k ** (1 / 3)
return A, k, y, y_us_tfpShow the drawing code
# @title Drawing code: Fig. 6, the two production functions
def add_production_functions(fig, country, A):
"""The U.S. production function (A = 1) and the country's (its TFP), Fig. 6.
Arguments
fig : the figure to draw in
country : the country compared with the United States
A : its implied TFP, A-bar
"""
grid = np.linspace(0, 2, 300) # capital per person, 0 to 2
# the U.S. curve, y = k^(1/3), in blue
fig.add_trace(
go.Scatter(
x=grid,
y=grid ** (1 / 3),
name="U.S. production function, A = 1",
line=dict(color=BLUE, width=3),
)
)
# the country's curve, y = A k^(1/3), in teal: lower by the factor A
fig.add_trace(
go.Scatter(
x=grid,
y=A * grid ** (1 / 3),
name=f"{short(country)}'s production function, A = {A:.2f}",
line=dict(color=TEAL, width=3),
)
)Show the drawing code
# @title Drawing code: Fig. 6, the United States and another country
def compare_with_us(country="China"):
"""The U.S. production function (A = 1) and another country's, in 2023.
Draws Fig. 6 (adapted from Fig. 4.6 in Jones); the country's production function
uses its implied TFP.
Arguments
country : the country compared with the United States
Returns
a Plotly figure
"""
# Step 1: the numbers of the figure (tfp_comparison)
A, k, y, y_us_tfp = tfp_comparison(country)
# Step 2: three points: the U.S., the country with U.S. TFP, and the country
names = ["U.S.", f"{short(country)} with U.S. TFP", short(country)]
points = pd.DataFrame({"label": names, "k": [1.0, k, k], "y": [1.0, y_us_tfp, y]})
fig = px.scatter(
points,
x="k",
y="y",
text="label",
hover_name="label",
labels={
"k": "Capital per person, k (U.S. = 1)",
"y": "Output per person, y (U.S. = 1)",
},
)
fig.update_traces(
marker=dict(color="black", size=12),
textposition=["top center", "bottom right", "bottom right"],
)
# Step 3: the two production functions (add_production_functions)
add_production_functions(fig, country, A)
# Step 4: the title reports the country's income with U.S. TFP and with its own
fig.update_layout(
title=(
f"{short(country)}: {y_us_tfp:.0%} of U.S. income with U.S. TFP, "
f"{y:.0%} with its own (A = {A:.2f})"
),
xaxis_range=[0, max(1.3, 1.2 * k)],
# no tick in the corner
yaxis=dict(range=[0, 1.6], tickvals=[0.25, 0.5, 0.75, 1.0, 1.25, 1.5]),
)
return fig# Fig. 6 with a menu of the countries of the sample
interact(
compare_with_us,
country=Dropdown(options=sorted(sample.country), value="China",
description="country", style=WIDE),
);Show the code of the website version
# @title Website version: Fig. 6 with a precomputed slider for the country
# one picture of Fig. 6 per country compared with the United States
slider_figure(
compare_with_us,
"country",
["China", "India", "Brazil", "Mexico", "Nigeria", "Japan"],
start="China",
label="compared with",
)Sources. Data: Penn World Table 11.0, Groningen Growth and Development Centre (DOI 10.34894/FABVLR), variables rgdpo (real GDP), pop (population) and csh_i (investment share); cite as Feenstra, Inklaar and Timmer (2015), “The Next Generation of the Penn World Table”, American Economic Review 105(10). Capital per person is our own perpetual-inventory estimate from PWT investment, and TFP is computed from it. Prepared file: data/ch04/pwt_production.csv (see the data notes). Chart: adapted from Fig. 4.6 in Jones (2024), Macroeconomics, 6th ed.
What to notice.
- In 2023, China had 35 percent of U.S. capital per person. With U.S. productivity, it would have about 71 percent of U.S. income. Its actual income is 30 percent, so its implied TFP is 0.42.
- The textbook, using 2019 data from an older release of the Penn World Table, finds a TFP of 0.35 for China. The newer release revises China’s income up, and China has also kept growing since 2019.
- Choose Japan: it has almost as much capital per person as the United States, but much lower TFP.
Your turn. Compare India with China. Which of the two has the larger gap between its “with U.S. TFP” point and its actual income? What does that tell you about its TFP?
Hint
The vertical distance between the two points of a country is the effect of its lower TFP. A bigger gap means a lower \(\bar{A}\). Check with sample[sample.country == "India"].
# Your turn: try it hereFig. 7: Measuring TFP so the model fits exactly
Goal. See how TFP varies across all 110 countries, and over time.
We plot implied TFP against income, both on ratio scales. Press the play button to move through the years. Capital per person in the earliest years depends heavily on a starting guess, so we begin in 1980.
First the data: the textbook’s sample in six years, without Venezuela as in Fig. 5.
# The sample in six years, one picture each; `.isin([...])` keeps the rows whose
# year is in the list. Then drop Venezuela, as in Fig. 5
years = pwt[pwt.in_sample & pwt.year.isin([1980, 1990, 2000, 2010, 2019, 2023])]
years = years[years.country != "Venezuela (Bolivarian Republic of)"]Show the drawing code
# @title Drawing code: Fig. 7, implied TFP against income, year by year
# Step 1: one point per country. `animation_frame="year"` draws one picture per
# year and adds a play button and a slider; `range_x` and `range_y` fix the axes,
# so that the points move but the axes do not
fig = px.scatter(
years,
x="y_rel",
y="tfp",
hover_name="country",
animation_frame="year",
log_x=True,
log_y=True,
range_x=[1 / 110, 2.5],
range_y=[1 / 40, 2.5],
labels={
"y_rel": "Per capita GDP (U.S. = 1, ratio scale)",
"tfp": "Implied TFP (U.S. = 1, ratio scale)",
"year": "Year",
},
title=(
"Rich countries have high TFP; "
"in the poorest it is below 1/10 of the U.S. level"
),
)
# Step 2: teal points, and the fraction ticks of Fig. 5 on both axes
fig.update_traces(marker=dict(color=TEAL, size=9))
fig.update_xaxes(tickvals=FRACTIONS, ticktext=FRACTION_NAMES)
fig.update_yaxes(tickvals=FRACTIONS, ticktext=FRACTION_NAMES)
fig.show()Sources. Data: Penn World Table 11.0, Groningen Growth and Development Centre (DOI 10.34894/FABVLR), variables rgdpo (real GDP), pop (population) and csh_i (investment share); cite as Feenstra, Inklaar and Timmer (2015), “The Next Generation of the Penn World Table”, American Economic Review 105(10). Capital per person is our own perpetual-inventory estimate from PWT investment, and TFP is computed from it. Prepared file: data/ch04/pwt_production.csv (see the data notes). Chart: inspired by Fig. 4.7 in Jones (2024), Macroeconomics, 6th ed.
What to notice.
- The points form a tight upward cloud: rich countries have high TFP, and poor countries have low TFP.
- The differences are large. In the poorest countries TFP is less than 1/10 of the U.S. level.
- Hover over the top right: Ireland, Norway, Singapore and Switzerland all have higher TFP than the United States. A few countries, such as Egypt and Syria, have high TFP for their income level.
Table 4.4: implied TFP for the textbook’s countries
Per capita GDP equals \(\bar{k}^{1/3}\) times implied TFP. The textbook’s values for 2019 are in the last column.
# Table 4.4: income, the capital part k^(1/3) and implied TFP (equation 8) in 2019,
# next to the textbook's TFP
table_4_4 = pd.DataFrame({
"observed y": y2019.y_rel,
"k^(1/3)": y2019.k_rel ** (1 / 3),
"implied TFP": y2019.tfp,
"textbook: implied TFP": [1.000, 1.054, 0.654, 0.703, 0.721, 0.792,
0.402, 0.347, 0.365, 0.247, 0.074],
})
table_4_4.round(3) # rounded to three decimals| observed y | k^(1/3) | implied TFP | textbook: implied TFP | |
|---|---|---|---|---|
| country | ||||
| United States | 1.000 | 1.000 | 1.000 | 1.000 |
| Switzerland | 1.252 | 1.159 | 1.080 | 1.054 |
| Japan | 0.645 | 0.995 | 0.648 | 0.654 |
| Italy | 0.693 | 0.938 | 0.739 | 0.703 |
| Spain | 0.666 | 0.899 | 0.741 | 0.721 |
| United Kingdom | 0.738 | 0.902 | 0.818 | 0.792 |
| Brazil | 0.253 | 0.588 | 0.430 | 0.402 |
| China | 0.265 | 0.659 | 0.402 | 0.347 |
| South Africa | 0.210 | 0.552 | 0.379 | 0.365 |
| India | 0.111 | 0.437 | 0.255 | 0.247 |
| Burundi | 0.015 | 0.169 | 0.088 | 0.074 |
Key numbers: capital or TFP?
Equation (7) splits the income gap between two countries into a TFP part and a capital part (equation 9):
\[\frac{y^*_{rich}}{y^*_{poor}} = \frac{\bar{A}_{rich}}{\bar{A}_{poor}} \times \left(\frac{\bar{k}_{rich}}{\bar{k}_{poor}}\right)^{1/3}\]
The textbook compares the five richest and the five poorest countries in 2019: incomes differ by a factor of 75, capital explains a factor of about 5 and TFP a factor of about 15. We redo the calculation for 2023.
# Rich and poor in 2023: how much of the income gap do capital and TFP explain?
# Step 1: the five poorest and the five richest countries of the sample
ranked = sample.sort_values("y_rel")
poorest, richest = ranked.head(5), ranked.tail(5)
# Step 2: equation (9): the income ratio is the TFP ratio times (k ratio)^(1/3)
income_ratio = richest.y_rel.mean() / poorest.y_rel.mean()
capital_factor = (richest.k_rel.mean() / poorest.k_rel.mean()) ** (1 / 3)
tfp_factor = income_ratio / capital_factor # whatever capital does not explain
# Step 3: show them. `", ".join(...)` glues a list of names into one line of text
print("Five richest:", ", ".join(richest.country))
print("Five poorest:", ", ".join(poorest.country))
print(f"Income ratio, rich / poor: {income_ratio:.0f}")
print(f"Factor explained by capital: {capital_factor:.1f}")
print(f"Factor explained by TFP: {tfp_factor:.1f}")Five richest: Switzerland, Luxembourg, Norway, Singapore, Ireland
Five poorest: Burundi, D.R. of the Congo, Central African Republic, Mozambique, Madagascar
Income ratio, rich / poor: 82
Factor explained by capital: 5.2
Factor explained by TFP: 15.9
Our income ratio for 2023 is a little larger than the textbook’s 75 for 2019. The split is very close: capital explains a factor of about 5, and TFP about three times as much. In logarithms, capital explains a bit more than one-third of the gap (\(\log 5.2 / \log 82 \approx 0.37\)).
We can do the same for Burundi and the United States. With capital differences only, Burundi’s income would be \(\bar{k}^{1/3}\) of the U.S. level. With TFP differences only, it would be its TFP.
# Burundi against the United States: capital differences only, or TFP only
# Step 1: Burundi's row; `.iloc[0]` takes the first (only) row
burundi = sample[sample.country == "Burundi"].iloc[0]
# Step 2: its income with only capital differences, k^(1/3), and with only TFP
capital_only, tfp_only = burundi.k_rel ** (1 / 3), burundi.tfp
# Step 3: print them as fractions of U.S. income
print(f"Actual income relative to the U.S.: 1/{1 / burundi.y_rel:.0f}")
print(f"With capital differences only: 1/{1 / capital_only:.1f}")
print(f"With TFP differences only: 1/{1 / tfp_only:.1f}")
# Because y = A k^(1/3), log y is the sum log A + log k^(1/3): the logs split the
# gap into shares (`np.log` is the natural logarithm)
print(
"Share of the (log) gap due to capital: "
f"{np.log(capital_only) / np.log(burundi.y_rel):.0%}"
)Actual income relative to the U.S.: 1/74
With capital differences only: 1/6.0
With TFP differences only: 1/12.2
Share of the (log) gap due to capital: 42%
Your turn. Repeat the rich–poor calculation with the ten richest and ten poorest countries. Does the split between capital and TFP change much?
Hint
Replace .head(5) and .tail(5) with .head(10) and .tail(10) in a copy of the cell above.
# Your turn: try it hereA figure we cannot draw: Korea at night (textbook Fig. 4.8)
Why is TFP so different across countries? The textbook points to human capital, technology, institutions and misallocation. Its most striking example is a satellite photograph of the Korean peninsula at night. South Korea is full of light, while North Korea is almost completely dark except for its capital, Pyongyang. Reliable income data for North Korea do not exist, so this figure cannot be rebuilt from data. You can see a similar photograph, taken from the International Space Station, at NASA’s The Koreas at Night.
Reform and growth: China and Russia
Two large economies changed their institutions after 1990 in very different ways. We compare how their incomes moved since then.
Fig. 8: Per capita GDP in China and Russia
Goal. Compare two very different paths of economic reform after 1990.
Russia reformed quickly, a “big bang” that swept away the old institutions at once. China reformed gradually. The figure shows what happened to income per person.
First the economics. income_growth picks the chosen countries since 1990 and computes the average growth of GDP per person a year of each.
def income_growth(countries):
"""GDP per person since 1990, and its average growth a year (Fig. 8).
Average annual growth, in percent: 100 ((last / first)^(1 / years) - 1)
Arguments
countries : the chosen countries
Returns
chosen : their rows of `pwt` since 1990 that have GDP per person
growth : {country: average growth a year in percent, rounded to 0.1}
missing : the short names of the countries with no data since 1990
"""
# Step 1: the rows since 1990; `.dropna(subset=["gdp_pc"])` drops the rows
# without GDP per person
chosen = pwt[pwt.country.isin(countries) & (pwt.year >= 1990)]
chosen = chosen.dropna(subset=["gdp_pc"])
# Step 2: each country's growth from its first to its last year
growth, missing = {}, []
for name in countries:
one = chosen[chosen.country == name]
if len(one) > 1:
years = one.year.iloc[-1] - one.year.iloc[0]
factor = one.gdp_pc.iloc[-1] / one.gdp_pc.iloc[0]
# rounded to 0.1; + 0.0 avoids "-0.0"
growth[name] = round(100 * (factor ** (1 / years) - 1), 1) + 0.0
else:
missing.append(short(name))
return chosen, growth, missingShow the drawing code
# @title Drawing code: Fig. 8, GDP per person since 1990
# the ticks of the ratio scale
RATIO_TICKS = [100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000]
def income_paths(countries=("China", "Russian Federation"), scale="linear"):
"""GDP per person since 1990 for the chosen countries, in 2021 dollars.
Draws Fig. 8 (inspired by Fig. 4.9 in Jones).
Arguments
countries : the countries drawn, in this order
scale : "linear" or "ratio", the vertical scale
Returns
a Plotly figure
"""
# Step 1: the data and the growth rates (income_growth)
chosen, growth, missing = income_growth(countries)
# Step 2: one line per country, each in its color of COLORS, in the chosen order
fig = px.line(
chosen,
x="year",
y="gdp_pc",
color="country",
log_y=(scale == "ratio"),
color_discrete_map=COLORS,
category_orders={"country": list(countries)},
labels={
"gdp_pc": "Per capita GDP (2021 dollars)",
"year": "Year",
"country": "Country",
},
)
fig.update_traces(line_width=3)
# Step 3: the title reports the growth of the first two countries;
# `parts[:2]` keeps the first two items of the list
parts = []
for name in growth:
parts.append(f"{short(name)} {growth[name]:+.1f}%")
title = "Growth of GDP per person a year since 1990: " + ", ".join(parts[:2])
if missing or not parts:
title = (
"No data since 1990 for "
f"{', '.join(missing) or 'the chosen countries'}"
)
fig.update_layout(
title=title,
xaxis=dict(range=[1990, 2023], tickvals=[1995, 2000, 2005, 2010, 2015, 2020]),
)
if scale == "ratio":
fig.update_yaxes(tickvals=RATIO_TICKS)
return fig# Fig. 8 with a list of countries and buttons for the vertical scale
interact(
income_paths,
countries=SelectMultiple(options=sorted(pwt.country.unique()),
value=("China", "Russian Federation"),
rows=8, description="countries", style=WIDE),
scale=ToggleButtons(options=["linear", "ratio"],
description="vertical scale"),
);Show the code of the website version
# @title Website version: Fig. 8 with every country, region menu and scale buttons
# Step 1: China and Russia first, then every other country, A to Z.
# `set(a) - {...}` drops the two from the set of all countries
fig = income_paths(
countries=("China", "Russian Federation")
+ tuple(sorted(set(pwt.country) - {"China", "Russian Federation"}))
)
fig.update_layout(title=income_paths().layout.title.text) # the textbook view's title
# Step 2: two buttons switch the vertical axis between a linear and a ratio scale
fig.update_layout(
updatemenus=[
dict(
type="buttons",
direction="right",
x=0.01,
y=0.98,
xanchor="left",
yanchor="top",
buttons=[
dict(
label="linear scale",
method="relayout",
args=[{
"yaxis.type": "linear",
"yaxis.autorange": True,
"yaxis.tickvals": None,
}],
),
dict(
label="ratio scale",
method="relayout",
args=[{
"yaxis.type": "log",
"yaxis.autorange": True,
"yaxis.tickvals": RATIO_TICKS,
}],
),
],
)
]
)
# Step 3: the region menu: China and Russia are drawn, the others a click away
country_menu(
fig,
["China", "Russian Federation"],
regions.set_index("country").region,
)Sources. Data: Penn World Table 11.0, Groningen Growth and Development Centre (DOI 10.34894/FABVLR), variables rgdpo (real GDP), pop (population) and csh_i (investment share); cite as Feenstra, Inklaar and Timmer (2015), “The Next Generation of the Penn World Table”, American Economic Review 105(10). GDP per person is rgdpo / pop, in 2021 dollars. Regions for the website menu: World Bank country and lending groups. Prepared files: data/ch04/pwt_production.csv, data/ch04/regions.csv (see the data notes). Chart: inspired by Fig. 4.9 in Jones (2024), Macroeconomics, 6th ed.
On the website: choose a region in the menu above the chart, then click a country in the legend to add it.
What to notice.
- Russia’s income fell for most of the 1990s and only passed its 1990 level again in 2006.
- China’s income rose steadily, by more than 7 percent a year.
- On the ratio scale, China’s line is close to straight: it grew at a similar rate for three decades.
Key numbers: Russia and China
# Key numbers for Russia and China. `.set_index("year").gdp_pc` gives GDP per
# person with the years as labels, so that russia[1990] is its value in 1990
russia = pwt[pwt.country == "Russian Federation"].set_index("year").gdp_pc
china = pwt[pwt.country == "China"].set_index("year").gdp_pc
# the first year after 1990 in which Russia's income is above its 1990 level
recovered = russia[(russia.index > 1990) & (russia > russia[1990])].index[0]
# ":,.0f" writes a number with a comma for thousands and no decimals, and
# `.idxmin()` gives the label (here the year) of the lowest value
print(
f"Russia, 1990: {russia[1990]:,.0f} dollars; "
f"lowest point {russia.min():,.0f} in {russia.idxmin()}"
)
print(f"Russia's fall from 1990 to 1999: {1 - russia[1999] / russia[1990]:.0%}")
print(f"First year above the 1990 level: {recovered}")
print(f"China, 1990 and 2019: {china[1990]:,.0f} and {china[2019]:,.0f} dollars")
# average annual growth over the 29 years from 1990 to 2019
print(
"China's average growth, 1990-2019: "
f"{100 * ((china[2019] / china[1990]) ** (1 / 29) - 1):.1f}% a year"
)Russia, 1990: 18,735 dollars; lowest point 9,617 in 1999
Russia's fall from 1990 to 1999: 49%
First year above the 1990 level: 2006
China, 1990 and 2019: 1,923 and 17,973 dollars
China's average growth, 1990-2019: 8.0% a year
The textbook says Russia’s income fell by 40 percent, but its own data show a fall of about 48 percent from 1990 to 1999, and ours about 49 percent. For China the textbook reports growth of about 6 percent a year. The newer Penn World Table revises China’s income in 1990 down and in 2019 up, which raises the growth rate to about 8 percent.
Your turn. Add Poland and Ukraine to the chart. Both left communism around 1990. Which of the two looks more like Russia, and which more like China?
Hint
Hold Ctrl (or Cmd) and click “Poland” and “Ukraine” in the list. Then switch to the ratio scale to compare growth rates.
# Your turn: try it hereConcluding remarks
- The Cobb-Douglas production function, equation (1), \(Y = \bar{A}K^{1/3}L^{2/3}\), has constant returns to scale but a diminishing marginal product of each input on its own, equations (2) and (3).
- Firms hire each input until its marginal product equals its price. In equilibrium, equation (4), all capital and labor are employed, \(r^* = \frac{1}{3}\bar{A}(\bar{L}/\bar{K})^{2/3}\) and \(w^* = \frac{2}{3}\bar{A}(\bar{K}/\bar{L})^{1/3}\). Capital earns one-third of output and labor two-thirds, whatever the amounts of capital and labor (equation 5), and payments exhaust output, so profit is zero (equation 6).
- Per person, the model says \(y^* = \bar{A}\bar{k}^{1/3}\), equation (7). With equal productivity, it predicts much smaller income gaps than we see, because of diminishing returns to capital.
- Measured as the residual that makes the model fit, equation (8), TFP differs enormously across countries. The split of income gaps in equation (9) shows that it explains most of the gap between rich and poor, roughly two-thirds to three-quarters of it.
- The production model takes capital as given. The next chapter, on the Solow model, explains where capital per person comes from.
Practice problems
These problems put the chapter to work in three ways: conceptual problems apply the production model to real cases, data-based problems send you back to the Penn World Table data, and modeling problems use the interactive model to answer policy questions. Each problem is marked Basic, Intermediate or Challenge; try it on your own first, and only then open the Hint and the Solution. The sliders of the modeling problems run in Colab (open the notebook with the badge at the top); on the website, use the website version of each figure above.
Conceptual problems
Problem 1 (Basic): The U.S. economy through the lens of the model. In 2024, U.S. GDP was 29,381 billion dollars (U.S. Bureau of Economic Analysis, Gross Domestic Product (GDPA), via FRED), and on average 161.3 million people had a job (the mean of the twelve monthly values of the U.S. Bureau of Labor Statistics, Employment Level (CE16OV), via FRED). Treat GDP as the model’s output \(Y\) and employment as its labor \(L\).
According to the factor shares of equation (5), how much of this income went to labor, and how much to capital?
What average wage per worker does the hiring rule for labor, equation (3), imply? Give two reasons why it is higher than the pay that a typical worker sees on a paycheck.
Hint
Labor gets two-thirds of output and capital one-third. Equation (3) says \(w = \frac{2}{3}\cdot\frac{Y}{L}\): first compute output per worker, then take two-thirds of it. Think about what the model counts as “labor income”, and about the difference between an average and a typical value.
Your answer:
Solution
- By equation (5), labor receives \(w^*L^*/Y^* = 2/3\) of output and capital \(r^*K^*/Y^* = 1/3\):
\[w^*L^* = \tfrac{2}{3}\times 29{,}381 \approx 19{,}587 \text{ billion dollars}, \qquad r^*K^* = \tfrac{1}{3}\times 29{,}381 \approx 9{,}794 \text{ billion dollars}.\]
That is about $19.6 trillion for workers and $9.8 trillion for the owners of capital. The two add up to GDP, as equation (6) says.
- Output per worker is \(Y/L = 29{,}381\text{ billion} / 161.348\text{ million} \approx 182{,}097\) dollars a year. The hiring rule gives
\[w^* = \frac{2}{3}\cdot\frac{Y}{L} \approx \frac{2}{3}\times 182{,}097 \approx 121{,}398 \text{ dollars a year}.\]
A typical paycheck is smaller for two reasons. First, the model’s wage is all labor income per worker: it includes what employers pay for health insurance, pensions and payroll taxes, which never appear in take-home pay. Second, it is an average, and a few very high earners pull an average up; the typical (median) worker earns less than the average worker. The model has one kind of worker, so it cannot tell these apart.
Y_us, L_us = 29380.991e9, 161.348e6 # 2024 GDP in dollars and employment in people
print(f"(a) labor income {2 / 3 * Y_us / 1e9:,.0f} billion, capital income {Y_us / 3 / 1e9:,.0f} billion")
print(f"(b) output per worker {Y_us / L_us:,.0f} dollars; model wage w* = {2 / 3 * Y_us / L_us:,.0f} dollars")Problem 2 (Intermediate): Misallocation lowers measured TFP. A hypothetical country has two ice-cream firms with the same technology, \(Y_i = K_i^{1/3}L_i^{2/3}\) with \(\bar{A} = 1\), and one worker each. The country owns 8 machines. The old firm belongs to a well-connected family and gets cheap loans, so it runs 7 machines; a new start-up gets only 1.
Compute each firm’s output and total output.
Compute each firm’s marginal product of capital with equation (2). Which firm would add more output with one extra machine?
Move the machines one at a time from the old firm to the start-up. Which split of the 8 machines gives the most output, and by what percent does total output rise compared with (a)?
An economist sees only the totals: 8 machines, 2 workers and the output of (a). She measures TFP with equation (8), applied to the whole economy. What TFP does she find? Why would a competitive capital market, in which every firm pays the same rental price, avoid this loss? Hsieh and Klenow (2009) estimate that if capital and labor were spread across plants as evenly as in the United States, judged by their marginal products, manufacturing TFP would rise by 30 to 50 percent in China and by 40 to 60 percent in India.
Hint
With one worker, a firm’s output is just \(K_i^{1/3}\), and its MPK is \(\frac{1}{3}K_i^{-2/3}\). For (d), the whole economy’s TFP is total output divided by \(K^{1/3}L^{2/3}\) with \(K = 8\) and \(L = 2\). For the last question, recall the hiring rule for capital in each firm.
# Your answer hereYour answer:
Solution
The old firm makes \(7^{1/3} \approx 1.913\) tons and the start-up \(1^{1/3} = 1\) ton, so total output is about \(2.913\) tons.
By equation (2) with \(L_i = 1\), \(MPK_i = \frac{1}{3}K_i^{-2/3}\). For the old firm, \(\frac{1}{3}\times 7^{-2/3} \approx 0.091\); for the start-up, \(\frac{1}{3}\times 1^{-2/3} \approx 0.333\). One more machine adds almost four times as much output at the start-up.
Output for each split:
| Machines, old firm and start-up | Total output | MPK, old firm | MPK, start-up |
|---|---|---|---|
| 7 and 1 | 2.913 | 0.091 | 0.333 |
| 6 and 2 | 3.077 | 0.101 | 0.210 |
| 5 and 3 | 3.152 | 0.114 | 0.160 |
| 4 and 4 | 3.175 | 0.132 | 0.132 |
Output is largest with 4 machines in each firm, where the two marginal products are equal. It rises from 2.913 to 3.175 tons, by about 9.0 percent, with the same machines and workers.
- For the whole economy, \(K^{1/3}L^{2/3} = 8^{1/3}\times 2^{2/3} = 2\times 2^{2/3} \approx 3.175\). The measured TFP is
\[\bar{A} = \frac{2.913}{3.175} \approx 0.918,\]
about 8.2 percent below the true productivity of each firm, \(\bar{A} = 1\). Nothing is wrong with the technology: the loss comes from where the machines are. In a competitive market, each firm rents machines until \(MPK_i = r\). With one rental price for everybody, the marginal products are equal, and with the same technology and the same labor that means the same number of machines in each firm: the efficient split. Cheap loans for the connected firm lower its cost of capital below \(r\), so it keeps renting machines whose marginal product is low. Measured TFP, a residual, records the loss as lower productivity. Hsieh and Klenow’s estimates suggest that in China and India this kind of loss is large.
for K_old, K_new in [(7, 1), (6, 2), (5, 3), (4, 4)]:
Y_total = production(K_old, 1) + production(K_new, 1) # one worker in each firm
print(f"{K_old} and {K_new} machines: output {Y_total:.3f}; MPK {mpk(K_old, 1):.3f} and {mpk(K_new, 1):.3f}")
Y_total = production(7, 1) + production(1, 1)
print(f"Measured TFP with 7 and 1 machines: {Y_total / production(8, 2):.3f}")Problem 3 (Challenge): Why does Ireland top the TFP ranking? In our data for 2023, Ireland has the highest implied TFP of the 110 countries of Fig. 7, 1.50 times the U.S. level, with income per person 1.82 times and capital per person 1.77 times the U.S. level. Much of Ireland’s GDP, however, is income of multinational firms that have moved their headquarters, their patents and other intellectual property, or their fleets of leased aircraft to Ireland. Ireland’s Central Statistics Office therefore publishes a second measure, modified gross national income (GNI*), which starts from GDP and takes out the profits that these firms send abroad, the income of relocated headquarters, and the depreciation of their intellectual property and leased aircraft: in 2023 it was 55.5 percent of GDP (CSO, Annual National Accounts 2024).
Use equation (8) to explain why the measured TFP rises one for one with any overstatement of output. Besides efficiency, what else can end up in this residual?
Suppose GNI* measures Ireland’s income better than GDP, so that income per person is 55.5 percent of the measured value, while capital per person stays at 1.77. Treat U.S. GDP as a good measure of U.S. income. What are Ireland’s income per person and TFP relative to the United States now?
The same firms’ patents and aircraft are also counted in Ireland’s capital stock. Suppose, hypothetically, that Ireland’s true capital per person is only half the measured value. Recompute TFP with the income of (b). Which mistake moves measured TFP more, an error in output or an error in capital, and why?
Hint
Equation (8) is \(\bar{A} = y/\bar{k}^{1/3}\), so the elasticity of measured TFP is \(1\) with respect to \(y\) and \(-1/3\) with respect to \(\bar{k}\). In (b), multiply \(y\) by 0.555 and keep \(\bar{k}\); in (c), also multiply \(\bar{k}\) by 0.5.
Your answer:
Solution
TFP is not observed: it is whatever makes equation (7) hold, \(\bar{A} = y/\bar{k}^{1/3}\). If measured output is 10 percent too high, measured TFP is 10 percent too high as well, since \(y\) enters with an exponent of 1. So the residual collects everything that raises measured income per person for given capital: true efficiency, but also natural resources (oil, gas, minerals), human capital, and plain measurement problems such as profits that firms book in a country for tax reasons.
Income per person becomes \(1.818\times 0.555 \approx 1.009\) times the U.S. level, about the same as the United States. Because \(\bar{k}\) does not change, TFP falls in the same proportion:
\[\bar{A} = 1.502\times 0.555 \approx 0.834.\]
With this TFP, Ireland would rank 13th of the 110 countries instead of first, between Austria and Sweden.
- Halving capital per person multiplies \(\bar{k}^{1/3}\) by \(0.5^{1/3} \approx 0.794\), so TFP is divided by 0.794, that is, multiplied by \(0.5^{-1/3} \approx 1.260\):
\[\bar{A} = \frac{1.009}{(0.5\times 1.773)^{1/3}} \approx 1.051.\]
The output error matters much more. Cutting output by 44.5 percent cut TFP by 44.5 percent, while halving capital raised TFP by only 26 percent, because capital enters with an exponent of only \(1/3\). Development accounting is therefore most sensitive to how we measure GDP. Ireland is the extreme case of a general lesson: before reading a country’s TFP as “efficiency”, check what its GDP measures.
Data-based problems
Problem 4 (Basic): Is capital more unequal than income? Load this chapter’s Penn World Table 11.0 data, pwt_production.csv, and keep the 110 countries of Fig. 5 in 2023 (the rows with in_sample true, without Venezuela). Compute the ratio of the 90th to the 10th percentile of capital per person, k_rel, and of income per person, y_rel. Which of the two is more unequal across countries? What income ratio would equation (7) predict from the capital ratio if every country had the same TFP?
Hint
cross.k_rel.quantile(0.9) gives the value below which 90 percent of the countries lie. Copy the two lines that build sample in Fig. 5, with your own variable name. With \(\bar{A}\) equal everywhere, \(y = \bar{k}^{1/3}\), so the predicted income ratio is the capital ratio to the power \(1/3\).
# Your answer hereSolution
We filter the year and the sample, then divide the two percentiles of each column.
panel = pd.read_csv(DATA_URL + "pwt_production.csv")
cross = panel[(panel.year == 2023) & panel.in_sample]
cross = cross[cross.country != "Venezuela (Bolivarian Republic of)"] # implausible GDP, as in Fig. 5
for column in ["k_rel", "y_rel"]:
ratio = cross[column].quantile(0.9) / cross[column].quantile(0.1)
print(f"{column}: 90th / 10th percentile = {ratio:.1f}")
k_ratio = cross.k_rel.quantile(0.9) / cross.k_rel.quantile(0.1)
print(f"Predicted income ratio with equal TFP: {k_ratio ** (1 / 3):.2f}")The country at the 90th percentile has 53.5 times the capital per person of the country at the 10th percentile, but only 24.2 times its income. Capital is about twice as unequal as income. With equal TFP, though, the model predicts an income ratio of only \(53.5^{1/3} \approx 3.77\), because of the strong diminishing returns to capital. The actual income ratio is more than six times larger than that, so most of the income differences must come from TFP.
Problem 5 (Intermediate): Catching up: capital or TFP? Between 1980 and 2023, China’s income per person rose from about 3 percent to 30 percent of the U.S. level. Equation (9) splits a change over time just as it splits a gap between two countries: the change in the log of relative income equals the change in the log of relative TFP plus one-third of the change in the log of relative capital per person. For China, South Korea (Republic of Korea in the data), Indonesia and Mexico, compute these three changes from 1980 to 2023, and the share of the income change that comes from capital. Which country’s catch-up relied most on capital? What happened in Mexico?
Hint
For one country, one = panel[panel.country == "China"].set_index("year") lets you write one.y_rel[2023]. The income change is np.log(one.y_rel[2023] / one.y_rel[1980]); the capital part divides the log change of k_rel by 3, and the TFP part is the log change of tfp. A loop over the four names prints one line per country.
# Your answer hereSolution
For each country we take logs of the 2023/1980 ratios. The capital and TFP parts add up to the income change.
panel = pd.read_csv(DATA_URL + "pwt_production.csv")
print("country income capital TFP capital share")
for name in ["China", "Republic of Korea", "Indonesia", "Mexico"]:
one = panel[panel.country == name].set_index("year")
income = np.log(one.y_rel[2023] / one.y_rel[1980]) # change in log relative income
capital = np.log(one.k_rel[2023] / one.k_rel[1980]) / 3 # one-third of the capital change
tfp = np.log(one.tfp[2023] / one.tfp[1980]) # change in log relative TFP
print(f"{name:18s} {income:6.2f} {capital:8.2f} {tfp:6.2f} {capital / income:9.2f}")| Country | Income change (log) | Capital part | TFP part | Capital share |
|---|---|---|---|---|
| China | 2.35 | 0.85 | 1.50 | 0.36 |
| South Korea | 1.49 | 0.68 | 0.81 | 0.46 |
| Indonesia | 0.94 | 0.69 | 0.25 | 0.73 |
| Mexico | −0.54 | −0.02 | −0.52 | 0.04 |
China’s relative income rose by a factor of about \(e^{2.35} \approx 10.5\). Capital explains about a third of this rise in logs and TFP about two-thirds, close to the split of the gap between rich and poor countries in the Key numbers after Fig. 7. Indonesia relied most on capital: almost three-quarters of its catch-up came from accumulating machines and buildings, and its TFP rose only a little relative to the United States. Mexico fell behind: its income per person dropped from 47 to 27 percent of the U.S. level, and almost all of the fall, 96 percent of it in logs, came from relative TFP, while its relative capital barely changed.
Problem 6 (Challenge): Where does capital earn the most? Lucas (1990) asked why so little capital flows from rich to poor countries. If India and the United States differed only in capital, he showed, the marginal product of capital in India would have to be about 58 times the U.S. one (Alfaro, Kalemli-Ozcan and Volosovych 2005). Check this puzzle with our data for 2023, using the 110 countries of Fig. 5 without the United States.
Equation (2) per person reads \(MPK = \frac{1}{3}y/\bar{k}\). Show that a country’s MPK relative to the United States is \(y_{rel}/k_{rel}\), and that with equal TFP it would be \(k_{rel}^{-2/3}\).
Compute both for each country. Merge in the income groups of
regions.csv(from the World Bank country and lending groups) and report the median of each measure by income group.Where is the return to capital highest? How does the measured gap compare with the gap the model predicts under equal TFP, and what does this suggest about Lucas’s puzzle?
Hint
- Divide the MPK of country \(i\) by the U.S. MPK: the \(\frac{1}{3}\) cancels. Then replace \(y_{rel}\) with \(k_{rel}^{1/3}\). (b)
cross.merge(groups[["country", "income_group"]], on="country")adds the income group to each row, and.groupby("income_group")[[...]].median()gives a table of medians.
# Your answer hereYour answer:
Solution
- For country \(i\) and the United States,
\[\frac{MPK_i}{MPK_{US}} = \frac{\frac{1}{3}\,y_i/\bar{k}_i}{\frac{1}{3}\,y_{US}/\bar{k}_{US}} = \frac{y_i/y_{US}}{\bar{k}_i/\bar{k}_{US}} = \frac{y_{rel}}{k_{rel}}.\]
With equal TFP, equation (7) gives \(y_{rel} = k_{rel}^{1/3}\), so \(MPK_{rel} = k_{rel}^{1/3}/k_{rel} = k_{rel}^{-2/3}\). A country with 1/8 of U.S. capital per person would then earn \(8^{2/3} = 4\) times the U.S. return.
- We build both columns, merge the income groups and take medians.
panel = pd.read_csv(DATA_URL + "pwt_production.csv")
groups = pd.read_csv(DATA_URL + "regions.csv")
cross = panel[(panel.year == 2023) & panel.in_sample]
cross = cross[(cross.country != "Venezuela (Bolivarian Republic of)") & (cross.country != "United States")]
cross = cross.merge(groups[["country", "income_group"]], on="country")
cross["mpk_equal_tfp"] = cross.k_rel ** (-2 / 3) # MPK relative to the U.S. with U.S. TFP
cross["mpk_measured"] = cross.y_rel / cross.k_rel # with each country's own TFP
order = ["Low income", "Lower middle income", "Upper middle income", "High income"]
table = cross.groupby("income_group")[["mpk_equal_tfp", "mpk_measured"]].median()
print(table.loc[order].round(2))
print(cross.groupby("income_group").size().loc[order])| Income group (countries) | MPK relative to the U.S., equal TFP | MPK relative to the U.S., measured |
|---|---|---|
| Low income (16) | 16.20 | 2.03 |
| Lower middle income (28) | 7.77 | 1.34 |
| Upper middle income (28) | 3.10 | 1.23 |
| High income (37) | 1.12 | 0.89 |
- The return to capital is highest in the low-income countries, and it falls with income in both columns, so the model points the right way: capital should flow toward poor countries. But the size is very different. With equal TFP, the typical low-income country would earn 16 times the U.S. return; with its own, much lower TFP, it earns only about twice the U.S. return. Low TFP makes each machine less productive, and that removes most of the reason to move capital, which goes a long way toward solving Lucas’s puzzle. The remaining gap may reflect risk and weak institutions: Alfaro, Kalemli-Ozcan and Volosovych find that in 1970–2000 low institutional quality, for example poorly protected property rights, was the leading explanation of why so little capital flows to poor countries. Keep in mind that our capital stocks are estimates built from past investment, so the numbers for single countries are rough.
Modeling problems
Problem 7 (Basic): An investment tax credit. A hypothetical ice-cream firm with 64 workers already rents 7 machines, and a machine rents for 1.5 tons of ice cream a year. The firm follows the hiring rule: it rents another machine only if the machine adds at least as much output as its rent. The slider below draws the firm’s production function of Fig. 1, and the title reports the output gain from the 8th machine.
Does the firm rent the 8th machine?
The government introduces an investment tax credit that cuts the firm’s cost of renting a machine to 1.3 tons a year. Does the firm now rent the 8th machine?
Move the workers slider. What is the smallest workforce on the slider at which the 8th machine pays for itself, without the credit and with it?
Hint
Compare the second number in the title, the gain “from K = 7 to 8”, with the cost of a machine. More workers make each machine more productive, so the gain grows as you move the slider to the right.
interact(production_curve,
L=FloatSlider(value=64, min=40, max=96, step=8, description="workers L", style=WIDE),
A=fixed(1.0), alpha=fixed(1 / 3));Source. The model of Fig. 1 above, adapted from Fig. 4.1 in Jones (2024), Macroeconomics, 6th ed.
# Your answer hereSolution
With 64 workers, the 8th machine adds 1.39 tons, less than its rent of 1.5 tons. The firm does not rent it: it would lose about 0.11 tons a year.
With the credit, the machine costs 1.3 tons, less than the 1.39 tons it adds. Now the firm rents it. By lowering the cost of capital, the credit moves the firm down its demand curve for capital, to more machines.
The gain from the 8th machine rises with the workforce:
| Workers | 48 | 56 | 64 | 72 |
|---|---|---|---|---|
| Gain from the 8th machine (tons) | 1.15 | 1.27 | 1.39 | 1.51 |
Without the credit, the smallest workforce on the slider for which the machine pays is 72 workers, where it adds 1.51 tons. With the credit, 64 workers are enough, while at 56 workers the gain of 1.27 tons is still below 1.3. The policy lowers the bar for investment, and firms with fewer workers start to invest too. One caution: in the economy as a whole, the supply of machines is fixed in this model, so a credit given to every firm would mostly push up the rental price that machine owners receive. The number of machines grows only as people invest over time, the subject of the Solow model in Chapter 5.
for L_firm in [48, 56, 64, 72]:
gain = production(8, L_firm) - production(7, L_firm) # extra output from the 8th machine
print(f"{L_firm} workers: the 8th machine adds {gain:.2f} tons")Problem 8 (Intermediate): Investment program or productivity reform? A finance minister must choose between two plans for an economy that starts at the baseline of Fig. 2, \(\bar{K} = \bar{L} = \bar{A} = 1\). Plan (i) is an investment program that raises the capital stock by 30 percent. Plan (ii) is a reform package (less red tape and more competition, say) that raises productivity \(\bar{A}\) by 10 percent. The number of workers does not change.
Which plan raises the wage more? Set the sliders for each plan and read \(w^*\) in the title.
What happens to the rental price \(r^*\) under each plan? And to the total income of capital owners, \(r^*\bar{K}\)?
How large would the investment program have to be to raise the wage as much as the reform? Bracket the answer with the slider, then compute it with equation (4).
Hint
Plan (i) is \(\bar{K} = 1.3\) with \(\bar{A} = 1\); plan (ii) is \(\bar{K} = 1\) with \(\bar{A} = 1.1\). By equation (4), the wage has an elasticity of \(1/3\) with respect to \(\bar{K}\) and of \(1\) with respect to \(\bar{A}\). For (c), solve \(\bar{K}^{1/3} = 1.1\).
interact(factor_markets,
K_bar=FloatSlider(value=1.0, min=1.0, max=1.5, step=0.1, description="supply of capital K̄", style=WIDE),
A=FloatSlider(value=1.0, min=1.0, max=1.2, step=0.05, description="productivity A", style=WIDE),
L_bar=fixed(1.0), alpha=fixed(1 / 3));Source. The model of Fig. 2 above, adapted from Fig. 4.2 in Jones (2024), Macroeconomics, 6th ed.
# Your answer hereSolution
At the baseline, \(w^* = 0.667\) and \(r^* = 0.333\). Plan (i) raises the wage to \(w^* = \frac{2}{3}\times 1.3^{1/3} \approx 0.728\), by 9.1 percent. Plan (ii) raises it to \(w^* = \frac{2}{3}\times 1.1 \approx 0.733\), by 10 percent. The reform raises the wage slightly more, although it changes its parameter by only a third as much.
Under plan (i), the rental price falls, to \(r^* = \frac{1}{3}\times 1.3^{-2/3} \approx 0.280\), by 16.0 percent: more machines means a lower marginal product of each one. Under plan (ii), the rental price rises by 10 percent, to 0.367. Total capital income still rises under both plans, because it is always one-third of output (equation 5): \(r^*\bar{K} \approx 0.280\times 1.3 \approx 0.364\) under plan (i), a rise of 9.1 percent, and \(0.367\) under plan (ii), a rise of 10 percent. Workers and capital owners both gain from either plan, but the reform raises everything, while the investment program lowers the return on each machine.
The wage is proportional to \(\bar{A}\bar{K}^{1/3}\). To match a 10 percent rise in \(\bar{A}\) with capital alone, \(\bar{K}^{1/3} = 1.1\), so \(\bar{K} = 1.1^3 \approx 1.331\): the capital stock would have to rise by 33.1 percent. On the slider, \(\bar{K} = 1.3\) gives a slightly lower wage than the reform, and \(\bar{K} = 1.4\) a higher one. Because of diminishing returns, a 1 percent rise in TFP is worth a 3 percent rise in the capital stock. The policy lesson is not that investment is useless, but that reforms which raise productivity can be worth a great deal of capital, and they usually cost far less than building it.
for K_plan, A_plan in [(1.0, 1.0), (1.3, 1.0), (1.0, 1.1), (1.331, 1.0)]:
w_plan, r_plan = mpl(K_plan, 1, A_plan), mpk(K_plan, 1, A_plan)
print(f"K = {K_plan}, A = {A_plan}: w* = {w_plan:.3f}, r* = {r_plan:.3f}, r*K = {r_plan * K_plan:.3f}")Problem 9 (Challenge): How can India double its relative income? In our data for 2023, India’s income per person is 12.3 percent of the U.S. level, its capital per person 9.4 percent and its TFP 0.27. Suppose the government sets the goal of doubling income per person relative to the United States, to about 24.6 percent. The sliders below multiply India’s capital per person and its TFP, and draw the result on the production functions of Fig. 6.
With today’s TFP, by what factor must capital per person rise to reach the goal?
With today’s capital, by what factor must TFP rise?
Hsieh and Klenow (2009), quoted in Problem 2, estimate that cutting misallocation to the U.S. level could raise TFP in Indian manufacturing by 40 to 60 percent. Suppose a reform raised India’s TFP by 40 percent in the whole economy. By what factor would capital per person still have to rise to reach the goal?
Capital follows productivity. In an open economy, investors add machines until the MPK is back at the return they can earn elsewhere. With the per-person MPK, \(\frac{1}{3}\bar{A}\bar{k}^{-2/3}\), find by what factor capital per person rises after the 40 percent TFP gain if the MPK returns to its old value. How far does India then get toward its goal?
Choose another country in the menu. Do your answers to (a) and (b) change? What would you tell a government that can fund either a large investment program or a productivity reform?
Hint
Equation (7) says \(y = \bar{A}\bar{k}^{1/3}\), so the factor by which \(y\) rises is the TFP factor times the cube root of the capital factor. In each part, write that product and set it equal to 2. In (d), write the MPK before and after the reform, and set the two equal.
def reform_chart(country="India", capital=1.0, tfp=1.0):
"""Fig. 6 after a policy that multiplies capital per person and TFP (equation 7)."""
fig = compare_with_us(country)
row = sample[sample.country == country].iloc[0]
k_new, A_new = capital * row.k_rel, tfp * row.tfp
y_new = A_new * k_new ** (1 / 3) # equation (7)
grid = np.linspace(0, 2, 300)
fig.add_trace(go.Scatter(x=grid, y=A_new * grid ** (1 / 3), name=f"After the policy, A = {A_new:.2f}",
mode="lines", line=dict(color=GREEN, width=3, dash="dash")))
fig.add_trace(go.Scatter(x=[k_new], y=[y_new], name="After the policy", mode="markers",
marker=dict(color=GREEN, size=15, symbol="star")))
fig.update_layout(title=f"{short(country)}: {y_new:.1%} of U.S. income after the policy, "
f"{row.y_rel:.1%} today ({y_new / row.y_rel:.2f} times)")
return fig
interact(reform_chart,
country=Dropdown(options=["India", "Bangladesh", "Pakistan", "Nigeria"], value="India",
description="country", style=WIDE),
capital=FloatSlider(value=1.0, min=1.0, max=8.0, step=0.5, description="capital per person ×", style=WIDE),
tfp=FloatSlider(value=1.0, min=1.0, max=2.0, step=0.1, description="TFP ×", style=WIDE));Source. The model of Fig. 6 above, with each country’s 2023 numbers from the Penn World Table 11.0 (pwt_production.csv), adapted from Fig. 4.6 in Jones (2024), Macroeconomics, 6th ed.
# Your answer hereYour answer:
Solution
With \(\bar{A}\) fixed, \(y\) doubles only if \(\bar{k}^{1/3}\) doubles, so \(\bar{k}\) must rise by a factor of \(2^3 = 8\): from 9.4 to about 75 percent of U.S. capital per person. Set capital to 8 and the title shows 24.6 percent, 2.00 times today’s income.
With \(\bar{k}\) fixed, \(\bar{A}\) must double, from 0.27 to 0.54. Set TFP to 2 and the title again shows 2.00 times.
The reform supplies a factor of 1.4, so capital must supply the rest: \(\bar{k}^{1/3}\) must rise by \(2/1.4 \approx 1.43\), and \(\bar{k}\) by
\[\left(\frac{2}{1.4}\right)^3 \approx 2.92.\]
On the sliders, TFP at 1.4 with capital at 2.5 falls just short of the goal, and capital at 3 just reaches it. A productivity reform cuts the capital India needs from 8 times to about 3 times today’s level.
- If the MPK returns to its old value, \(\bar{A}\bar{k}^{-2/3}\) is unchanged. With \(\bar{A}' = 1.4\bar{A}\),
\[1.4\,\bar{A}\,(\bar{k}')^{-2/3} = \bar{A}\,\bar{k}^{-2/3} \quad\Longrightarrow\quad \frac{\bar{k}'}{\bar{k}} = 1.4^{3/2} \approx 1.657.\]
Income then rises by \(1.4\times 1.657^{1/3} = 1.4\times 1.4^{1/2} = 1.4^{3/2} \approx 1.657\), that is, by 65.7 percent rather than 40 percent. India would reach about 20.3 percent of U.S. income, most of the way to its goal of 24.6 percent. The same exponent \(3/2\) on \(\bar{A}\) appears in the steady state of the Solow model (Chapter 5), where capital also responds to productivity.
- The factors do not change from country to country: 8 for capital alone and 2 for TFP alone, whatever the starting point, because the elasticities of equation (7), \(1/3\) for capital and \(1\) for TFP, are the same everywhere. For a government the lesson is that capital alone is a very expensive road to higher income, because each extra machine adds less than the one before. A reform that raises TFP pays off twice: directly, and by raising the return to capital, which attracts investment. The hard part is that the model does not say how to raise TFP; that is the question of institutions, technology and misallocation discussed above.
india = sample[sample.country == "India"].iloc[0]
now = india.tfp * india.k_rel ** (1 / 3) # equation (7) with today's values
target = 2 * india.y_rel
print(f"(a) capital factor with today's TFP: {(target / now) ** 3:.2f}")
print(f"(b) TFP factor with today's capital: {target / now:.2f}")
print(f"(c) capital factor after a 40% TFP gain: {(target / (1.4 * now)) ** 3:.2f}")
print(f"(d) capital and income factor: {1.4 ** 1.5:.3f}; India reaches {1.4 ** 1.5 * india.y_rel:.1%} of U.S. income")


