Level 1 · Economics: Markets and Measurement
Economics studies how people, firms and governments choose when things are scarce, and what happens when their choices meet. On this path it is a subject of models you compute. A model is a few precise statements with numbers in them: buyers want 300 - 12 * P kilograms of cheese at a price of P; a household's basket holds one month's rent, twelve loaves and thirty bus rides. The work is to compute what follows inside the model: the price at which a market clears, who really pays a tax, how fast an economy grew once rising prices are taken out. Economists argue about which models describe the world well, and some of those arguments are old and still open. So every problem on this path states its model in full (the equations, the numbers, the units, what happens at the edges) and asks only what follows from it. That is why each one has exactly one right answer. The stories (an island, a harbour town, a fish market) are there to make the numbers mean something; all data are invented.
Each level has a micro half and a macro half. Microeconomics looks at one market: the buyers' demand, the sellers' supply, the price where they meet, the value the trade creates, and what changes when incomes rise, costs go up, or a council caps the price or taxes every sale. Macroeconomics looks at a whole economy, and at this level the question is how to measure it: what GDP counts and what it leaves out, how to separate "more was produced" from "prices went up", what an inflation rate or a growth rate over twenty years really says, and who counts as unemployed.
The other paths meet this one in specific places. An auctioneer who raises the price when buyers are left wanting is a feedback loop, and a too-eager one overshoots exactly like the controllers of the Signals & Control path. Finding a price by halving an interval is the bisection of the Algorithms path, and the best trades in a market of single items come from sorting both sides and matching them. Price indexes and growth rates turn a series of numbers into a few honest summaries, which is the daily work of the Statistics path. And the free market's quantity is the one that makes total surplus as large as possible, an optimisation problem of the kind the Machine Learning path solves with gradients.
Demand: what buyers want at each price
A buyer's demand says how much they want at each price. On this path it is usually a straight line,
where P is the price, Q the quantity, a the quantity wanted when the good is free (a quantity, not a price) and b how many units less for every unit of currency on the price. A line keeps falling past zero, but nobody buys a negative amount: from the choke price a / b upwards the buyer wants nothing, so the quantity is \max(0, a - bP).
The market demand at a price is what all buyers together want at that same price: the sum of the individual demands. Three hotels on an island buy eggs every week (dozens, at P crowns a dozen):
hotels = [(90, 15), (60, 6), (40, 10)] # (a, b): each wants a - b * P dozen
for a, b in hotels:
print("choke price", a / b)
P = 5
each = [max(0, a - b * P) for a, b in hotels]
print("at", P, "crowns:", each, "-> market demand", sum(each))
print("without the floor at 0:", sum(a - b * P for a, b in hotels))
ps = [p / 4 for p in range(0, 41)]
plot([sum(max(0, a - b * p) for a, b in hotels) for p in ps], ps,
xlabel="dozens of eggs a week", ylabel="crowns per dozen", title="Market demand")
choke price 6.0
choke price 10.0
choke price 4.0
at 5 crowns: [15, 30, 0] -> market demand 45
without the floor at 0: 35
At 5 crowns the third hotel is past its choke price of 4 and buys nothing. Adding the raw lines would let its "minus 10 dozen" cancel part of the other orders and report 35 instead of 45. Press Run: economists draw price up and quantity across, so the chart is plot(quantities, prices). The market demand is not one straight line: it bends at 4 and at 6 crowns, where a hotel drops out, and reaches 0 at 10.
Common mistakes: clamping the total (max(0, sum(...))) instead of each buyer. Treating a as a price: it is the quantity at a price of 0. Starting the running total outside the loop over prices, so it carries over from one price to the next.
Practice: Every Café's Strawberries
Supply and the equilibrium
Supply says how much sellers offer at each price. As a line,
A higher price draws out more. When c is negative, the line reaches zero at P = -c/d: below that price sellers offer nothing, because it does not cover their costs.
The market is in equilibrium at the price where the quantity demanded equals the quantity supplied. For the two lines that is one linear equation:
and the quantity Q^* is either line at P^*. The slopes add in the denominator because one line falls while the other rises. A cheese market on market day (kg per week, crowns per kg):
a, b = 300, 12 # demand: Q = 300 - 12 P
c, d = -60, 18 # supply: Q = -60 + 18 P
print("buyers stop at", a / b, "crowns; sellers start at", round(-c / d, 2))
P = (a - c) / (b + d)
print("P* =", P, " wanted", a - b * P, " offered", c + d * P)
for p in (8, 16):
print("at", p, ": wanted", a - b * p, " offered", c + d * p)
ps = [p / 2 for p in range(0, 51)]
plot([max(0, a - b * p) for p in ps], ps, label="demand", xlabel="kg per week", ylabel="crowns per kg")
plot([max(0, c + d * p) for p in ps], ps, label="supply")
buyers stop at 25.0 crowns; sellers start at 3.33
P* = 12.0 wanted 156.0 offered 156.0
at 8 : wanted 204 offered 84
at 16 : wanted 108 offered 228
Press Run: the lines cross at 12 crowns and 156 kg. Below P^* more is wanted than offered (excess demand); above it more is offered than wanted (excess supply). Either way somebody is left unhappy at that price, which is why the equilibrium is the natural point to look at.
The formula always returns a number, but it is not always a market outcome. Here buyers stop at 10 crowns and sellers start at 12:
a, b, c, d = 50, 5, -120, 10
P = (a - c) / (b + d)
print("crossing at P =", round(P, 2), "and Q =", round(a - b * P, 2))
crossing at P = 11.33 and Q = -6.67
The lines do cross, but at a negative quantity: nothing is traded. Lines can also cross at a negative price, when sellers offer more than buyers want even at a price of 0. So compute both numbers first, then check both against 0; a model must say what to report when the check fails (the problems do).
Common mistakes: b - d in the denominator. Forgetting that a negative c makes a - c larger than a. Checking only the price, or only the quantity. Rejecting a crossing at exactly 0 when the problem counts it.
Practice: Where the Fish Market Clears
Value, cost and surplus
Read the lines the other way round and they put a value on each unit. Solve the demand line for the price: at quantity q,
is the most the buyer of the q-th unit would pay (their willingness to pay). Solve the supply line: \text{cost}(q) = (q - c)/d is the least the seller of the q-th unit would accept. The first units go to the keenest buyers and come from the cheapest sellers, so value falls and cost rises along the market:
a, b, c, d = 300, 12, -60, 18 # the cheese market again; P* = 12, Q* = 156
value = lambda q: (a - q) / b
cost = lambda q: (q - c) / d
for q in (0, 100, 156, 200):
print("unit", q, ": worth", round(value(q), 2), " costs", round(cost(q), 2))
unit 0 : worth 25.0 costs 3.33
unit 100 : worth 16.67 costs 8.89
unit 156 : worth 12.0 costs 12.0
unit 200 : worth 8.33 costs 14.44
Up to 156 kg each unit is worth more to its buyer than it costs its seller, so trading it creates value; beyond 156 it would destroy value. At the price P^* = 12 both sides gain:
- Consumer surplus: what buyers would have paid minus what they paid, the area between the demand line and the price P^*, from 0 to Q^*.
- Producer surplus: what sellers received minus the least they would have accepted, the area between P^* and the supply line.
With straight lines both are triangles with base Q^*, and \text{area} = \text{base} \times \text{height} / 2. The heights are prices: from the top of the demand line (a/b, the choke price) down to P^*, and from P^* down to the bottom of the supply line (-c/d).
a, b, c, d = 300, 12, -60, 18
P = (a - c) / (b + d)
Q = a - b * P
consumer = Q * (a / b - P) / 2
producer = Q * (P - (-c / d)) / 2
print("consumer surplus", round(consumer, 2), " producer surplus", round(producer, 2))
print("total", round(consumer + producer, 2), "crowns a week")
consumer surplus 1014.0 producer surplus 676.0
total 1690.0 crowns a week
The total, 1690 crowns a week, is the area between the demand and the supply line from 0 to Q^*: the value the market creates. The producer triangle needs c \le 0; with c > 0 sellers offer something even at a price of 0 and the area is a trapezoid, so problems that ask for surplus say which case they use.
Common mistakes: using a (a quantity) as a height. Dropping the minus sign in -c / d. Returning negative areas for a market where nothing is traded.
Practice: What the Honey Market Is Worth
Elasticity and revenue
How strongly do buyers react to a price change? Raw changes depend on units (a price rise of 1 means one thing in cents and another in crowns), so economists compare percentage changes. The price elasticity of demand is the percentage change in quantity divided by the percentage change in price.
Percentages have a trap: from 2.00 to 2.50 is +25 %, but back from 2.50 to 2.00 is -20 %. The midpoint formula divides each change by the average of its two values, so both directions give the same size:
For demand e is negative (dearer, fewer), so its size |e| names it: elastic when |e| > 1 (quantity moves proportionally more than price), inelastic when |e| < 1, unit elastic when |e| = 1. Revenue is price times quantity, P \cdot Q.
def midpoint_elasticity(p1, q1, p2, q2):
pct_q = (q2 - q1) / ((q1 + q2) / 2)
pct_p = (p2 - p1) / ((p1 + p2) / 2)
return pct_q / pct_p
# a bus fare from 2.00 to 2.50, and a museum ticket from 12 to 15
for p1, q1, p2, q2 in ((2.0, 5000, 2.5, 4400), (12, 900, 15, 600)):
e = midpoint_elasticity(p1, q1, p2, q2)
print("e =", round(e, 4), " |e| > 1:", abs(e) > 1, " revenue", p1 * q1, "->", p2 * q2)
print("the fare back down:", round(midpoint_elasticity(2.5, 4400, 2.0, 5000), 4))
e = -0.5745 |e| > 1: False revenue 10000.0 -> 11000.0
e = -1.8 |e| > 1: True revenue 10800 -> 9000
the fare back down: -0.5745
The bus riders are inelastic (|e| \approx 0.57): the fare rose by 22 % (midpoint), riders fell by only 13 %, and revenue rose. The museum's visitors are elastic (|e| = 1.8) and the till took in less. That is the general rule, and the midpoint formula makes it exact: a price rise raises revenue when demand is inelastic, lowers it when elastic, and leaves it unchanged when |e| = 1. Going back down gives the same elasticity, which is the point of the midpoint.
Common mistakes: dividing by the old values q1 and p1 (the bus gives -0.48 instead). Labelling by e rather than abs(e), so every demand looks inelastic. Testing abs(e) == 1 without a tolerance. Putting the price change on top.
Practice: Dearer Tickets, Fuller Till?
Shifts of the curves
A model earns its keep when something changes. Comparative statics is the method: solve the model, change one parameter, solve again, compare. The hard part is translating the story into parameters.
- Demand shifts when something other than the price changes what buyers want: incomes, tastes, a festival in town. If buyers want
extramore at every price, the intercept becomesa + extra; the slope stays. - Supply shifts when costs change. If each unit costs sellers
kmore to make, they now needkmore for the same quantity: they offer at price P what they used to offer at P - k. The new line is c + d(P - k) = (c - dk) + dP: the line moves up byk, which for a line of slopedis the interceptc - d * k.
A change in the price itself moves the market along the curves, not the curves. Back to the cheese market: a summer festival adds 60 kg of demand at every price, and dearer milk raises the cost of each kilogram by 2 crowns.
def crossing(a, b, c, d):
P = (a - c) / (b + d)
return round(P, 4), round(a - b * P, 4)
a, b, c, d = 300, 12, -60, 18
k = 2
print("before: ", crossing(a, b, c, d))
print("festival: ", crossing(a + 60, b, c, d))
print("dearer milk: ", crossing(a, b, c - d * k, d))
print("both: ", crossing(a + 60, b, c - d * k, d))
print("new supply at 15 =", c - d * k + d * 15, "= old supply at 13 =", c + d * 13)
before: (12.0, 156.0)
festival: (14.0, 192.0)
dearer milk: (13.2, 141.6)
both: (15.2, 177.6)
new supply at 15 = 174 = old supply at 13 = 174
More demand raises both price and quantity. A cost rise raises the price and lowers the quantity: buyers pay 1.2 crowns of the 2-crown cost rise, and buy less. With both, the price surely rises, but the quantity depends on which push is bigger; here demand wins. The last line checks the translation: after the cost rise, sellers want 2 crowns more for the same 174 kg.
Common mistakes: adding k to the intercept (that moves supply right, as if costs fell) instead of subtracting d * k. Comparing equilibria with == when deciding "no change". Reporting what happened to a curve instead of to the equilibrium.
Practice: Dearer Flour, Richer Customers
Price ceilings and floors
Sometimes a rule fixes where the price may go. A price ceiling forbids prices above a level (a cap on rents or on fuel); a price floor forbids prices below one (a minimum price for milk, a minimum wage). A rule only matters if it forbids the equilibrium: a ceiling binds when it is below P^*, a floor when it is above P^*. A rule exactly at P^*, or on the other side, changes nothing.
When a rule binds, the price sits at the rule's level, where the two sides want different amounts. Nobody can be made to buy or sell more than they want, so the quantity traded is the short side: the smaller of the two. The difference is a shortage under a ceiling (more wanted than offered) or an excess supply under a floor. As always, neither side's amount goes below 0.
a, b, c, d = 300, 12, -60, 18 # P* = 12, Q* = 156
for kind, level in (("ceiling", 9), ("ceiling", 14), ("floor", 16), ("ceiling", 2)):
binds = level < 12 if kind == "ceiling" else level > 12
if not binds:
print(kind, level, ": does not bind, the market stays at 12 crowns and 156 kg")
continue
wanted = max(0, a - b * level)
offered = max(0, c + d * level)
print(kind, level, ": wanted", wanted, " offered", offered, " traded", min(wanted, offered),
" gap", abs(wanted - offered))
ceiling 9 : wanted 192 offered 102 traded 102 gap 90
ceiling 14 : does not bind, the market stays at 12 crowns and 156 kg
floor 16 : wanted 108 offered 228 traded 108 gap 120
ceiling 2 : wanted 276 offered 0 traded 0 gap 276
A ceiling of 9 crowns leaves 192 kg wanted and only 102 offered: 102 kg change hands and 90 kg are missing. The quantity traded falls under both rules, because the short side decides. At a ceiling of 2, sellers offer nothing at all (the formula says -24; the floor at 0 says 0).
Aside (optional): do minimum wages work like this floor? In this model, a wage floor above the equilibrium means fewer jobs than people who want them. Some economists find that a good guide to labour markets; others point to studies of actual minimum-wage rises that found small or no effects on employment, and to models in which a few large employers set wages (economists call that monopsony), where a moderate floor can even raise employment. The evidence is argued over to this day. Problems here never ask which view is right: they state the model and ask what follows in it.
Common mistakes: treating every ceiling as binding. Reporting the amount wanted as the amount traded. Forgetting max(0, ...) when the level is far from P^*. Calling a rule exactly at P^* binding.
Practice: The Council's Firewood Rules
A per-unit tax: two prices
A tax of t per unit drives a wedge into the price. Buyers pay P_b, the government takes t, and sellers keep P_s = P_b - t. Buyers respond to what they pay and sellers to what they keep, so the market clears where
Compared with P^* = (a - c)/(b + d), buyers pay \frac{d}{b + d}\,t more, and sellers keep \frac{b}{b + d}\,t less. Who bears the tax (its incidence) depends only on the slopes, not on who hands the money over: had sellers been taxed, they would need P_s + t to supply, and the same equation would follow.
a, b, c, d = 300, 12, -60, 18
t = 3
P_star = (a - c) / (b + d)
Pb = (a - c + d * t) / (b + d)
Ps = Pb - t
Q = a - b * Pb
print("buyers pay", round(Pb, 2), " sellers keep", round(Ps, 2), " quantity", round(Q, 2),
" check:", round(c + d * Ps, 2))
print("revenue", round(t * Q, 2), " buyers bear", round((Pb - P_star) / t, 4), "of the tax")
buyers pay 13.8 sellers keep 10.8 quantity 134.4 check: 134.4
revenue 403.2 buyers bear 0.6 of the tax
Buyers pay 1.80 crowns more than the 12 of before, sellers keep 1.20 less: buyers bear 60 % here, because d = 18 is larger than b = 12. The side that cuts back its quantity less when its price moves (here the buyers) bears more of the tax. The government collects t \cdot Q; the quantity fell from 156 to 134.4 kg.
Common mistakes: Ps = Pb + t. Computing the quantity from the sellers' price on the demand line. Measuring the buyers' share as Pb / t, or from the wrong starting price.
Practice: Who Pays the Oyster Levy?
Deadweight loss
The surplus section measured the value a market creates as the area between demand and supply over the units traded. A free market trades every unit up to Q^*, every unit worth more than it costs. A tax or a binding ceiling cuts the quantity to some Q_1 < Q^*, and the units between Q_1 and Q^*, each worth more to a buyer than it costs a seller, are not traded. The value lost on them is the deadweight loss: the triangle between the two lines from Q_1 to Q^*. Its base is Q^* - Q_1, and its height is the gap between the lines at Q_1, \text{value}(Q_1) - \text{cost}(Q_1).
a, b, c, d = 300, 12, -60, 18
value = lambda q: (a - q) / b
cost = lambda q: (q - c) / d
Q_star = 156
def loss(Q1):
return (Q_star - Q1) * (value(Q1) - cost(Q1)) / 2
print("tax of 3: Q1 = 134.4, gap", round(value(134.4) - cost(134.4), 2), " loss", round(loss(134.4), 2))
print("ceiling of 9: Q1 = 102, gap", round(value(102) - cost(102), 2), " loss", round(loss(102), 2))
print("no trade: Q1 = 0, gap", round(value(0) - cost(0), 2), " loss", round(loss(0), 2))
tax of 3: Q1 = 134.4, gap 3.0 loss 32.4
ceiling of 9: Q1 = 102, gap 7.5 loss 202.5
no trade: Q1 = 0, gap 21.67 loss 1690.0
For a tax that leaves some trade, the gap at Q_1 is exactly the tax: the buyer of the last unit pays P_b, its seller keeps P_b - t. For a ceiling the gap is not the distance from P^* to the ceiling (3 crowns): at 102 kg the next kilogram is worth 16.50 to a buyer and costs only 9 to make. And a policy that stops all trade loses the whole 1690 crowns of surplus. Tax revenue is not part of the loss: it moves from buyers and sellers to the government, and consumer surplus, producer surplus, revenue and deadweight loss add up to the 1690 of the free market.
Common mistakes: using the ceiling's distance from P^* as the height. Using the tax as the height when the tax is so large that nothing trades. Counting the tax revenue as lost.
Practice: The Rides That Never Happen
Searching for the price
How does a market find P^* without anyone solving an equation? One story: an auctioneer calls a price, counts what is wanted and what is offered, and adjusts. The excess demand E(P) = D(P) - S(P) is positive below P^* and negative above it, so a natural rule moves the price in its direction:
with k > 0 how strongly the auctioneer reacts. Near the equilibrium, each round multiplies the distance to P^* by 1 - k(b + d). A small k creeps up; a larger one overshoots and swings back; too large, and the swings grow:
a, b, c, d = 300, 12, -60, 18 # P* = 12
for k in (0.02, 0.06, 0.08):
P, calls = 5.0, [5.0]
for _ in range(12):
wanted = max(0, a - b * P)
offered = max(0, c + d * P)
P = max(0, P + k * (wanted - offered))
calls.append(P)
print("k =", k, ":", [round(p, 2) for p in calls[:7]])
plot(calls, label=f"k = {k}", xlabel="round", ylabel="price called")
k = 0.02 : [5.0, 9.2, 10.88, 11.55, 11.82, 11.93, 11.97]
k = 0.06 : [5.0, 17.6, 7.52, 15.58, 9.13, 14.29, 10.16]
k = 0.08 : [5.0, 21.8, 0, 24.0, 0, 24.0, 0]
Press Run. With k = 0.02 the factor is 1 - 0.6 = 0.4 and the price closes in from below. With 0.06 it is -0.8: each call overshoots, by less each time. With 0.08 it is -1.4: the swings grow until the floor at 0 catches them, and the calls bounce between 0 and 24 for ever. This is the feedback loop of the Signals & Control path: excess demand is the error, k the gain, and too much gain makes a loop unstable.
When demand and supply are sums of many clamped rules, the totals bend at every choke price and every seller's starting price, and no single formula holds everywhere. One fact survives the bends: E(P) never rises as P rises. A function like that crosses zero at most once, and bisection finds the crossing: keep a lo where E > 0 and a hi where E < 0, test the middle, and keep the half that still has a sign change. Here the hotels of the first section buy from two farms; a farm with rule (m, s) offers nothing up to its minimum price m and s \cdot (P - m) above it:
hotels = [(90, 15), (60, 6), (40, 10)] # buyers (a, b)
farms = [(3, 10), (6, 25)] # sellers (m, s)
def demand(p):
return sum(max(0, a - b * p) for a, b in hotels)
def supply(p):
return sum(max(0, s * (p - m)) for m, s in farms)
lo, hi = 0, 10 # E(0) = 190 > 0; at 10, the highest choke price, E < 0
for _ in range(60):
mid = (lo + hi) / 2
if demand(mid) - supply(mid) > 0:
lo = mid
else:
hi = mid
print("price", round(lo, 6), " quantity", round(demand(lo), 4))
print("check on the stretch 5..6: (150 + 30) / (21 + 10) =", round((150 + 30) / 31, 6))
price 5.806452 quantity 28.0645
check on the stretch 5..6: (150 + 30) / (21 + 10) = 5.806452
Each halving gains one binary digit, so 60 rounds pin the price far below any tolerance. Between 5 and 6 crowns nothing switches on or off (the third hotel left at 4, the second farm joins at 6), so there demand is 150 - 21P and supply 10P - 30, and one linear equation confirms the answer. Adding all the rules into one big line instead would ignore the bends and give a wrong price. If every seller's minimum price is at or above every buyer's choke price, no price gives a positive quantity, and there is no trade to find.
Common mistakes: leaving out one of the floors at 0 in the loop (each changes the path). Updating the price before both quantities are computed from the old one. Bisecting on an interval whose ends have the same sign.
Practice: The Wool Auctioneer's Calls, The Island Tomato Exchange
A market of single units
Lines are a convenient summary, but a small market is a list of people. Each buyer wants one item and would pay at most their value; each seller has one item and would sell for no less than their cost. Which trades make the total surplus (values of the buyers served minus costs of the sellers who sell) as large as possible?
Sort and match. Line the buyers up from the highest value down and the sellers from the lowest cost up, which draws the demand and supply "staircases", and pair them off in that order while the next buyer's value is at least the next seller's cost. Any other choice can be improved by swapping in a keener buyer or a cheaper seller. A weekend market for used surfboards:
values = [300, 120, 250, 90, 180]
costs = [100, 200, 60, 260, 150]
v = sorted(values, reverse=True)
c = sorted(costs)
print(v)
print(c)
k = surplus = 0
while k < len(v) and k < len(c) and v[k] >= c[k]:
surplus += v[k] - c[k]
k += 1
print(k, "trades, total surplus", surplus)
[300, 250, 180, 120, 90]
[60, 100, 150, 200, 260]
3 trades, total surplus 420
The fourth buyer (120) values a board less than the fourth seller (200) needs, and every later pair is worse, so the matching stops. Who is paired with whom does not change the total; only which buyers and sellers trade does. A pair with value equal to cost adds exactly 0, so a problem has to say whether it counts.
A single market price can organise these trades if nobody wants to change sides at it: everyone who trades is happy to, everyone left out is happy to stay out. The price must be at least 150 (or the third seller refuses) and at most 180 (or the third buyer does); the people left out add their own limits (the buyer at 120 must not want a board, the seller at 200 must not want to sell), which here do not bind. So any price from 150 to 180 works: the market's price is decided at the margin, by the last people in and the first people out.
Common mistakes: sorting both lists the same way. Running past the end of the shorter list. Pairing buyers and sellers in their original order.
Practice: The Textbook Swap
From formula to Python
Economic formulas are mostly sums, and a sum becomes a generator expression inside sum. The index under \sum becomes the for, what it runs over becomes the iterable, and the term becomes the expression in front of for.
- Market demand D(P) = \sum_i \max(0,\ a_i - b_i P) is
sum(max(0, a - b * P) for a, b in buyers). - The value of an economy's output, price times quantity summed over goods, \sum_g p_g\, q_g, is
sum(prices[g] * quantities[g] for g in prices)when both are dicts keyed by good. With a list per good (one entry per year), yeartissum(prices[g][t] * quantities[g][t] for g in prices): the formula's subscript t becomes the index[t]. - A count is a sum of ones: the number of buyers still in the market at price P, \sum_i [a_i - b_i P > 0], is
sum(a - b * P > 0 for a, b in buyers), becauseTruecounts as 1. - A formula with a name becomes a function: P^* = \frac{a - c}{b + d} is
def eq_price(a, b, c, d): return (a - c) / (b + d), and a triangle's area isbase * height / 2. - A product of growth factors, \prod_t (1 + g_t), has no built-in like
sum;math.prod(1 + g for g in rates)does it, or a loop that multiplies.
import math
buyers = [(40, 2), (25, 5), (60, 3)]
P = 6
print("demand", sum(max(0, a - b * P) for a, b in buyers), " buyers in:", sum(a - b * P > 0 for a, b in buyers))
prices = {"rice": [2.0, 2.5], "tea": [8.0, 8.0], "cloth": [15.0, 12.0]}
quantities = {"rice": [300, 320], "tea": [40, 45], "cloth": [20, 30]}
for t in range(2):
print("year", t, "output worth", sum(prices[g][t] * quantities[g][t] for g in prices))
rates = [0.10, -0.10, 0.05]
print("growth factor", round(math.prod(1 + g for g in rates), 4), " not", round(1 + sum(rates), 4))
demand 70 buyers in: 2
year 0 output worth 1220.0
year 1 output worth 1520.0
growth factor 1.0395 not 1.05
The last line is a warning in one number: +10 % then -10 % does not get you back where you started (1.1 × 0.9 = 0.99), so rates compound by multiplying, never by adding. When a problem gives a formula, name every symbol in code first (which list is p, what t indexes, where the sum runs), then write the generator.
GDP: counting what an economy produces
Gross domestic product (GDP) is the market value of all final goods and services newly produced in a country in a year. Each word excludes something:
- final: an intermediate good, bought by a firm and used up making something else (flour for a bakery), is left out, because its value is already inside the price of the bread. Counting both would count the flour twice.
- newly produced: a used car or an old house changing hands was counted in the year it was made. Buying shares or bonds produces nothing either; it only moves ownership.
- goods and services: a transfer (a pension, a benefit) pays money without anything produced in return.
One way to measure GDP adds up who spent on final output: households (consumption, C), firms buying new machines and buildings, plus new houses (investment, I), the government buying goods and services (G), and foreigners buying exports (X). Part of that spending went on goods made abroad, so imports (M) are subtracted:
Subtracting imports does not mean imports lower GDP: an imported phone a household buys is first counted in C, then taken out again in M, so it adds nothing, as it should. A dict collects totals per kind; d.get(k, 0) reads a key that may be missing as 0:
ledger = [("C", 61), ("I", 12), ("G", 9), ("X", 14), ("M", 11), ("C", 4),
("transfer", 7), ("intermediate", 6), ("used", 3)]
totals = {}
for kind, amount in ledger:
totals[kind] = totals.get(kind, 0) + amount
print(totals)
C, I, G = totals.get("C", 0), totals.get("I", 0), totals.get("G", 0)
NX = totals.get("X", 0) - totals.get("M", 0)
print("C + I + G + NX =", C, "+", I, "+", G, "+", NX, "=", C + I + G + NX)
{'C': 65, 'I': 12, 'G': 9, 'X': 14, 'M': 11, 'transfer': 7, 'intermediate': 6, 'used': 3}
C + I + G + NX = 65 + 12 + 9 + 3 = 89
The pensions, the intermediate inputs and the used goods sit in the dict but never enter the sum. Net exports are 3: the country sold abroad slightly more than it bought.
The second way counts at the producers. A firm's value added is its sales minus the inputs it bought from other firms: what the firm itself produced, paid out as wages, rent and profit. Summing value added over all domestic firms gives GDP without any double counting. Wool from a farm becomes yarn, then sweaters; the knitter also imports buttons:
sales = [("farm", "spinner", 30), ("spinner", "knitter", 70), ("knitter", "households", 160)]
sold, bought = {}, {}
for seller, buyer, amount in sales:
sold[seller] = sold.get(seller, 0) + amount
bought[buyer] = bought.get(buyer, 0) + amount
bought["knitter"] += 10 # buttons from abroad
for firm in ("farm", "spinner", "knitter"):
print(firm, "adds", sold[firm] - bought.get(firm, 0))
print("total", sum(sold[f] - bought.get(f, 0) for f in ("farm", "spinner", "knitter")),
" vs adding every sale:", sum(amount for _, _, amount in sales))
farm adds 30
spinner adds 40
knitter adds 80
total 150 vs adding every sale: 260
The value added sums to 150: the 160 of sweaters minus the 10 of buttons made abroad. Adding every sale gives 260, counting the wool three times.
Aside (optional): what GDP leaves out. GDP counts market production. Cooking at home, caring for a parent and volunteering are left out; so is leisure, and so are pollution and the using-up of forests or fish stocks, unless someone pays for them. It says nothing about how output is shared. Some economists see these gaps as reasons to report broader measures of well-being next to GDP, or to adjust it for environmental damage; others answer that GDP was built to measure production, does that consistently, and should not be stretched into a measure of welfare. Problems here use GDP exactly as each one defines it.
Common mistakes: adding imports. Counting intermediate purchases, transfers, used goods or shares. Counting a pension as government spending (G is only what the government buys). Forgetting a kind that never appears, so a later totals["I"] raises a KeyError.
Practice: The Island's Spending Ledger, From Wheat Field to Bread Shelf
Nominal and real GDP
If GDP rose by 30 %, was 30 % more produced, or did prices go up? Nominal GDP values each year's output at that year's prices; real GDP values every year's output at the prices of one fixed base year b, so only quantities can move it:
The data come as a dict of lists: one list per good, one entry per year. The number of years is the length of any one list, and each formula is the generator expression of the previous sections with the year as an index:
prices = {"coffee": [3, 4, 5], "bikes": [200, 210, 260]} # crowns per unit
quantities = {"coffee": [500, 520, 560], "bikes": [10, 12, 12]} # units per year
n = len(next(iter(prices.values())))
base = 0
nominal = [sum(prices[g][t] * quantities[g][t] for g in prices) for t in range(n)]
real = [sum(prices[g][base] * quantities[g][t] for g in prices) for t in range(n)]
print("nominal", nominal)
print("real ", real)
print("growth over two years: nominal", round(nominal[2] / nominal[0] - 1, 4), " real", round(real[2] / real[0] - 1, 4))
nominal [3500, 4600, 5920]
real [3500, 3960, 4080]
growth over two years: nominal 0.6914 real 0.1657
Nominal GDP rose by 69 %, real GDP by under 17 %: most of the rise was prices. In the base year the two are equal (same prices, same quantities), which is a quick check on any code. A different base year gives different real levels, and even somewhat different real growth, because it weights the goods by different relative prices; the last section of this lesson returns to that.
Common mistakes: swapping the roles (base-year quantities at this year's prices measure prices, not output). Assuming the base is year 0. Relying on the goods coming in some order instead of looping over the keys.
Practice: Same Catch, New Prices
Price indexes and inflation
A price index turns many prices into one number that reads 100 in its base year. Two are standard.
The GDP deflator compares nominal and real GDP. Both value the same quantities, one at today's prices and one at the base year's, so their ratio is pure price change:
A consumer price index (CPI) prices one fixed basket, the quantities a typical household buys in a month, year after year. The basket never changes, so only prices can change its cost c_t, and \text{CPI}_t = 100 \cdot c_t / c_b.
Rates on this path are fractions: 0.05 means 5 %. The inflation rate is the growth rate of a price index, a ratio minus 1:
nominal, real = [3500, 4600, 5920], [3500, 3960, 4080] # from the previous section
deflator = [100 * nom / r for nom, r in zip(nominal, real)]
print("deflator", [round(x, 2) for x in deflator])
basket = {"rent": 1, "bread": 12, "bus": 30}
prices = {"rent": [500, 520, 560], "bread": [2.5, 2.6, 2.8], "bus": [1.2, 1.2, 1.5], "taxi": [20, 22, 21]}
cost = [sum(q * prices[g][t] for g, q in basket.items()) for t in range(3)]
cpi = [100 * x / cost[0] for x in cost]
print("basket cost", [round(x, 2) for x in cost], " CPI", [round(x, 2) for x in cpi])
print("inflation", [round(cpi[t] / cpi[t - 1] - 1, 4) for t in range(1, 3)])
print("index points gained in year 2:", round(cpi[2] - cpi[1], 2))
deflator [100.0, 116.16, 145.1]
basket cost [566.0, 587.2, 638.6] CPI [100.0, 103.75, 112.83]
inflation [0.0375, 0.0875]
index points gained in year 2: 9.08
The sum runs over the basket, not over every price on record: the taxi fares are not in it. In year 2 the CPI gained 9.08 index points but prices rose by 8.75 %: points equal per cent only when the previous year reads exactly 100. Always take the ratio.
The two indexes can disagree, because they weight prices differently: the deflator covers everything the economy produces, with this year's quantities; the CPI only what households buy, with the basket's fixed quantities (imported goods included, investment goods left out).
Common mistakes: inflation as a difference of index points. Pricing goods that are not in the basket. Averaging the price changes of the goods without weighting them by quantity. Using a calendar year such as 2030 as a list index (years.index(2030) gives its position).
Practice: The Price of a Monthly Basket, What the Deflator Says
Rebasing, old money and splicing
The 100 of an index is arbitrary: an index means something only as a ratio. \text{index}_y / \text{index}_s says how many times more the basket cost in year y than in year s. Three uses follow.
Rebasing moves the 100 to year y: divide every value by \text{index}_y and multiply by 100. All ratios stay the same.
Money from another year. An amount x from year s is worth, in year y's money,
Check the direction with common sense: if prices rose since s, old money converts into more of today's money.
Splicing. When a statistics office starts a new index and stops the old one, the two are joined at a link year L that both cover. The old series is multiplied by one factor, \text{new}_L / \text{old}_L, so it agrees with the new one at L and keeps all its own year-to-year changes. If the series share several years, the model must say which is the link, and which value wins in a shared year.
index = {2005: 72.0, 2015: 96.0, 2025: 120.0}
rebased = {y: 100 * v / index[2025] for y, v in index.items()}
print("rebased to 2025:", rebased)
fare_2005, fare_2025 = 1.20, 2.10
in_2025_money = fare_2005 * index[2025] / index[2005]
print("the 2005 fare in 2025 crowns:", round(in_2025_money, 2), " real rise", round(fare_2025 / in_2025_money - 1, 4))
old = {2010: 100.0, 2011: 103.0, 2012: 107.0}
new = {2012: 100.0, 2013: 102.5}
factor = new[2012] / old[2012]
spliced = {y: round(v * factor, 2) for y, v in old.items() if y not in new}
spliced.update(new)
print("spliced:", spliced)
rebased to 2025: {2005: 60.0, 2015: 80.0, 2025: 100.0}
the 2005 fare in 2025 crowns: 2.0 real rise 0.05
spliced: {2010: 93.46, 2011: 96.26, 2012: 100.0, 2013: 102.5}
A bus fare of 1.20 crowns in 2005 is 2.00 crowns of 2025 money: the fare of 2.10 is 5 % dearer in real terms, not 75 %. In the spliced series, 2010 to 2011 still rises by 3 %, as the old index said.
Common mistakes: turning the ratio upside down, so old money shrinks. Assuming an index reads 100 in the year you care about. Splicing by subtracting instead of multiplying, which changes every growth rate. Dividing the wrong way round (old[L] / new[L]).
Practice: Grandmother's Wage in Today's Money, Stitching Two Price Indexes
Growth, compounding and doubling
The growth rate from x_0 to x_1 is x_1 / x_0 - 1. Growth compounds: each year's rate applies to a level that already includes the earlier growth, so n years at a constant rate g multiply by (1 + g)^n. Turning that around gives the compound annual growth rate (CAGR), the one constant rate that takes x_0 to x_n:
where n is the number of years between them: for a list of len(values) yearly values, n = len(values) - 1. The average of the yearly rates is not the CAGR; ups and downs do not cancel.
How long until a quantity growing at g doubles? Solve (1 + g)^T = 2 with logarithms: T = \ln 2 / \ln(1 + g). The rule of 70 is a mental shortcut: T \approx 70 / (100\,g), 70 divided by the rate in per cent. It comes from \ln 2 \approx 0.693 and \ln(1 + g) \approx g for small g, so it gets worse as growth gets faster.
import math
gdp = [200, 260, 208, 250]
yearly = [gdp[t] / gdp[t - 1] - 1 for t in range(1, len(gdp))]
n = len(gdp) - 1
g = (gdp[-1] / gdp[0]) ** (1 / n) - 1
print("yearly", [round(r, 4) for r in yearly], " their average", round(sum(yearly) / n, 4))
print("CAGR", round(g, 4), " check:", round(gdp[0] * (1 + g) ** n, 6))
for rate in (0.02, 0.0772, 0.25):
print("at", rate, ": rule of 70 says", round(70 / (100 * rate), 2), " exact", round(math.log(2) / math.log(1 + rate), 2))
yearly [0.3, -0.2, 0.2019] their average 0.1006
CAGR 0.0772 check: 250.0
at 0.02 : rule of 70 says 35.0 exact 35.0
at 0.0772 : rule of 70 says 9.07 exact 9.32
at 0.25 : rule of 70 says 2.8 exact 3.11
Three years of +30 %, -20 % and +20 % average 10 % a year, yet the economy only grew by a factor of 1.25, which is 7.7 % a year compounded. At 2 % the rule of 70 is off by about a day; at 25 % by almost four months.
Per person. GDP can grow while each person's share shrinks, if the population grows faster. GDP per capita is GDP divided by the population. A population that grows by a rate each year compounds the same way, each rate applied to the previous year's population, and per capita growth is close to GDP growth minus population growth:
gdp = [800, 840, 870]
pop_growth = [0.03, 0.04] # from year 0 to 1, and from 1 to 2
pop = [40]
for r in pop_growth:
pop.append(pop[-1] * (1 + r))
per_capita = [x / p for x, p in zip(gdp, pop)]
print("population", [round(p, 3) for p in pop])
print("per capita", [round(x, 3) for x in per_capita])
print("year 2: GDP", round(gdp[2] / gdp[1] - 1, 4), " per capita", round(per_capita[2] / per_capita[1] - 1, 4))
population [40, 41.2, 42.848]
per capita [20.0, 20.388, 20.304]
year 2: GDP 0.0357 per capita -0.0041
In year 2 GDP grew by 3.6 % but the population by 4 %, so the average person got slightly poorer: 3.57 - 4 \approx -0.43 %, close to the exact -0.41 %.
Common mistakes: n = len(values). Averaging yearly rates. Forgetting the factor 100 in the rule of 70. Applying every population rate to the first year's population instead of compounding. A doubling time for a rate of 0 or less, which never doubles.
Practice: How Long Until It Doubles?, A Bigger Pie, Thinner Slices
Who counts as unemployed
"Unemployed" in the statistics is narrower than "without a job". A labour survey asks about one reference week and applies definitions in order (the problems state theirs; this is the usual shape):
- Only people of working age (say 16 and over) are counted at all.
- Employed: did any paid work that week, even one hour.
- Unemployed: no paid work, and actively looked for a job recently, and could start soon.
- Everyone else of working age is not in the labour force: students, retired people, carers, and people who want a job but have stopped looking.
The labour force is the employed plus the unemployed. Each rate has its own denominator:
A student with a 3-hour weekend job is employed. A 30-year-old who lost her job and sends applications every week is unemployed. A 50-year-old who wants work but gave up looking months ago is outside the labour force.
working_age, employed, unemployed = 820, 492, 41
labour_force = employed + unemployed
print("unemployment", round(unemployed / labour_force, 4), " participation", round(labour_force / working_age, 4),
" employment", round(employed / working_age, 4))
# 25 people who had given up start looking again; nobody finds or loses a job
unemployed, labour_force = unemployed + 25, labour_force + 25
print("after:", round(unemployed / labour_force, 4), "unemployed,", round(labour_force / working_age, 4), "participate")
unemployment 0.0769 participation 0.65 employment 0.6
after: 0.1183 unemployed, 0.6805 participate
The unemployment rate rose from 7.7 % to 11.8 % although nobody lost a job: 25 people re-entered the labour force. Read the three rates together. A rate whose denominator is 0 (a survey with nobody in the labour force) has no value, and a problem says what to return.
Common mistakes: dividing the unemployed by the whole population or by the working-age count. Counting someone with a few hours of work who is also job-hunting as unemployed. Counting people below working age.
Practice: Who Counts as Unemployed?
Chained indexes in outline
Real GDP at fixed base-year prices depends on the base year when relative prices change fast: a good that got much cheaper (laptops, phones) weighs heavily at old prices and lightly at new ones. Statistics offices therefore often chain: measure each year's growth with the prices of that year and the year before, and multiply the yearly growth factors.
Write S(a, b) = \sum_{\text{goods}} p_a\, q_b, prices of year a times quantities of year b. Between two years there are two natural quantity growth factors:
- Laspeyres, at last year's prices: L = S(t-1, t) \,/\, S(t-1, t-1);
- Paasche, at this year's prices: P = S(t, t) \,/\, S(t, t-1);
- Fisher, their geometric mean: F = \sqrt{L \cdot P}.
import math
prices = {"tea": [4, 5], "phones": [300, 200]}
quantities = {"tea": [100, 105], "phones": [2, 4]}
def S(a, b):
return sum(prices[g][a] * quantities[g][b] for g in prices)
L = S(0, 1) / S(0, 0)
P = S(1, 1) / S(1, 0)
print("Laspeyres", round(L, 4), " Paasche", round(P, 4), " Fisher", round(math.sqrt(L * P), 4))
Laspeyres 1.62 Paasche 1.4722 Fisher 1.5443
At last year's prices phones were dear, so doubling their number counts for a lot (62 % growth); at this year's lower prices it counts for less (47 %). Fisher sits between. A chained index starts at 100 and multiplies one such link per year, and it can be turned back into money by scaling it to equal nominal GDP in a chosen year. The details are the problem's to state; what matters here is the idea: each link compares neighbours with up-to-date prices, and growth over many years is a product of links.
Common mistakes: pairing the wrong year's prices with the wrong year's quantities. Using the arithmetic mean (L + P)/2 instead of the geometric mean. Adding links instead of multiplying them.
Practice: Bread, Laptops and a Chain of Fisher Links
Practice
These problems are required on this path at this level; the path adds one problem from each of the level's topics, picked for you and kept.
- Where the Fish Market Clears
- What the Honey Market Is Worth
- Who Pays the Oyster Levy?
- The Island's Spending Ledger
- Same Catch, New Prices
- How Long Until It Doubles?
Checklist
You can now:
- Add individual demands into a market demand, with each buyer's quantity floored at 0 past their choke price.
- Find the equilibrium of linear demand and supply, P^* = (a - c)/(b + d), and recognise a crossing at a negative price or quantity.
- Read the lines as willingness to pay and cost, and compute consumer surplus, producer surplus and deadweight loss as triangles whose heights are prices.
- Compute a midpoint elasticity, name it from |e|, and predict what a price change does to revenue.
- Translate a story into a shifted curve (an income change moves the demand intercept; a cost rise
kis the supply interceptc - d * k) and compare equilibria. - Decide whether a ceiling or floor binds, find the short side and the gap, and find both prices, the revenue and the incidence of a per-unit tax.
- Search for a market price with a price-adjustment loop or by bisection on excess demand, and find the best trades of a single-unit market by sorting and matching.
- Measure GDP by spending and by value added, knowing what is left out and why imports are subtracted.
- Compute nominal and real GDP, a deflator and a fixed-basket CPI, and inflation as a ratio minus 1.
- Rebase and splice indexes, convert money between years, and compute growth rates, a CAGR, doubling times, per capita growth and labour-market rates with the right denominators.