Chapter 13 of 18 · Interactive Dashboard

Case Studies for Multiple Regression

Six econometric case studies — from production functions to causal inference — each with its own dataset and interactive visualization.

Cobb-Douglas production function

Does doubling capital and labor together exactly double output? The Cobb-Douglas form turns that question into a one-line test.

Taking natural logarithms of Q = AKαLβ turns a multiplicative production function into a linear regression. The log form ln(Q) = ln(A) + α·ln(K) + β·ln(L) is estimable by OLS, and its coefficients are elasticities — a 1% change in K raises Q by α%. Constant returns to scale (CRS) holds when α + β = 1 (doubling inputs exactly doubles output); it's testable as a joint restriction (Key Concept 13.3) using the F-test.
What you can do here
  • Read α, β, and their sum in the fit-stat cards.
  • Check the CRS p-value — if it's large, the data is consistent with constant returns to scale.
  • Compare the actual (cyan) and predicted (purple) output time series — the gap between them is the unexplained variation.
Try this
  1. Read α = 0.23 and β = 0.81 in the stat cards. A 1% rise in capital lifts output by 0.23%; a 1% rise in labor by 0.81% — labor dominates for this economy, consistent with a labor-share of ~80%.
  2. Read α + β = 1.04 and CRS p-value = 0.61. The sum is essentially 1 and we can't reject CRS at any conventional level — doubling both inputs doubles output.
  3. Eyeball the actual vs predicted output lines. R² = 0.96 over 24 years. An excellent fit — most of the output variation is traced back to the two observed inputs.

Take-away: Log-linearizing a multiplicative production function gives OLS directly-interpretable elasticities — and the CRS restriction becomes a clean joint hypothesis test. Read §13.2 in the chapter →

Phillips curve & omitted variables bias

A single chart broke a generation of monetary policy: the Phillips curve's sign flipped after 1970. The regression didn't lie — it just had the wrong variables.

When a relevant variable is omitted, its effect gets absorbed into the coefficients that remain. The omitted-variable bias formula says the expected bivariate coefficient equals the true coefficient plus βomitted × γ, where γ is the correlation between the omitted variable and the included regressor (Key Concept 13.5). In the Phillips curve, omitting expected inflation drove the negative unemployment coefficient to positive after 1970 (Key Concept 13.4) — a statistical sign flip, not an economic one.
What you can do here
  • Toggle between Pre-1970 and Post-1970 to see the slope flip sign.
  • Read the OVB decomposition in the callout — it shows how the bivariate slope breaks into the true slope plus bias.
  • Compare the augmented-model coefficient to the raw bivariate slope.
Try this
  1. Start on Pre-1970. Slope = −1.03. The classic trade-off: higher unemployment buys lower inflation — the curve policymakers relied on.
  2. Switch to Post-1970. Slope = +0.27 — the sign flipped. Same regression specification, opposite answer. The statistical result shocked economists and forced a rethink of monetary theory.
  3. Read the OVB formula in the callout. E[b₂] = β₂ + β₃γ = −0.13 + 1.15 × 0.34 ≈ +0.26 — matches the observed +0.27. Almost exact — the bias formula decomposes the sign flip into its components.
  4. Note the augmented-model coefficient (−0.13). Once expected inflation is added as a regressor, the negative Phillips relationship reappears — omitted variables were the whole story.

Take-away: A regression can reverse its own sign when a correlated determinant is omitted — the fix is to add the variable, not reject the theory. Read §13.3 in the chapter →

RAND Health Insurance Experiment (RCT)

When people pay more at the doctor, do they go less? The RAND experiment settled the question by flipping insurance plans at random — turning a policy debate into a causal estimate.

In a randomized control trial, random assignment makes treatment and control groups comparable on every characteristic — observed or unobserved. That balance eliminates selection bias and the omitted-variables concerns that plague observational studies. The RAND RCT randomly assigned families to insurance plans with different cost-sharing, so differences in medical spending across plans are causal estimates of moral hazard — not just correlations.
What you can do here
  • Compare mean annual spending across plans in the bar chart.
  • Read the F-test in the callout — a joint test that all plan coefficients are zero.
  • Hover bars to see the per-plan sample size.
Try this
  1. Compare free care ($2,154/year) to 95% cost-sharing ($1,046/year). People spend ~51% less when they pay out of pocket — moral hazard confirmed under random assignment.
  2. Read F = 11.39 (p < 0.001) in the callout. Plan assignment is jointly significant — insurance plan systematically shifts spending even after accounting for random within-plan variation.
  3. Notice R² = 0.007 — intentionally tiny. Most spending variation is idiosyncratic health shocks, not insurance plan. The RCT detects the signal anyway because random assignment strips out confounding, not noise.

Take-away: Random assignment is the gold standard — it buys causal identification at the price of running a controlled experiment rather than analyzing observational data. Read §13.5 in the chapter →

Difference-in-Differences: health clinic access

Treatment improved outcomes — but would outcomes have improved anyway? DiD answers by using the control group as a counterfactual.

Difference-in-differences compares changes over time between treatment and control groups, removing both time-invariant confounders and common time trends. The key identifying assumption is parallel trends: in the absence of treatment, both groups would have followed the same trajectory. When the assumption holds, DiD isolates the treatment effect from any secular trend that affects both groups equally. South African children in communities that gained health clinics improved more than children in communities that did not — the DiD estimator quantifies that differential.
What you can do here
  • Read the 2×2 DiD table — pre/post rows, control/treated columns.
  • Scan the change column — both groups rose, but treated rose more.
  • Inspect the line chart — the dashed gap between treated and counterfactual is the DiD estimate.
Try this
  1. Read both change rows in the table. Both control and treated z-scores rise 1993 → 1998. That's the common secular trend — something besides clinics improved child nutrition for everyone.
  2. Read DiD = 0.52 SD (treated change minus control change). Roughly the distance from the 25th to the 45th percentile of weight-for-age — a sizeable effect on top of the common trend.
  3. Check the 95% CI [0.06, 0.98] and p = 0.027. Just excludes zero; the interval is wide because clustered standard errors penalize within-community correlation.

Take-away: DiD uses the control group as a natural counterfactual — valid whenever the parallel-trends assumption holds, and a workhorse of modern policy evaluation. Read §13.6 in the chapter →

Regression discontinuity: incumbency advantage

A candidate who wins 50.01% of the vote and one who loses with 49.99% are nearly identical politicians. But one gets sworn in and one goes home — and that tiny gap is an entire natural experiment.

Regression discontinuity exploits sharp cutoffs in treatment assignment. Units just above and just below a threshold are nearly identical on every characteristic except treatment status — creating a quasi-experiment. In U.S. Senate elections, candidates who barely win versus barely lose are effectively randomized into incumbent/non-incumbent status; the jump in next-election vote share at margin = 0 estimates the incumbency advantage, which sits around 5–7 percentage points.
What you can do here
  • Toggle Binned means / Raw scatter to see the discontinuity through two lenses.
  • Watch the fitted line jump at margin = 0 — that jump is the treatment effect.
  • Read the CI in the fit stats — the more it excludes zero, the stronger the identification.
Try this
  1. Stay in Binned means and inspect the jump at margin = 0. 4.8 percentage points. Barely winning the election delivers ~5% more votes next time — the incumbency advantage made visible.
  2. Toggle to Raw scatter. The discontinuity is visible even without binning. The jump survives the noise — a sign the identification isn't an artifact of how the bins were drawn.
  3. Read the 95% CI [3.1, 6.5] and p < 0.001. The interval excludes zero comfortably — this is one of the most robust findings in modern political science.
  4. Look at the linear slopes on each side. Both roughly 0.35. A 10-point wider margin adds ~3.5% to next election vote within a side — useful for checking the local linearity assumption.

Take-away: A sharp rule plus fine data turns threshold assignments into experiments — RD is the gift that keeps giving in public policy and political science. Read §13.7 in the chapter →

Instrumental variables: institutions and GDP

Do good institutions cause growth, or does growth build good institutions? OLS can't separate the two — but a 19th-century settler-mortality map can.

When a regressor is endogenous (correlated with the error term), OLS is biased; instrumental variables rescue identification. An IV must be relevant (correlated with the endogenous regressor) and exogenous (uncorrelated with the outcome except through the regressor). Two-stage least squares (2SLS) first predicts the endogenous regressor from the instrument, then plugs that prediction into the second-stage regression. For institutions-and-growth, Acemoglu-Johnson-Robinson use historical settler mortality as the instrument — it shaped modern institutions but has no direct channel to modern GDP.
What you can do here
  • Toggle OLS / First stage / IV-2SLS to walk through the three regressions side-by-side.
  • Check the first-stage F — F > 10 is the rule of thumb for instrument relevance.
  • Compare the OLS and IV coefficients at the bottom of the fit stats — the gap is the endogeneity correction.
Try this
  1. Start on OLS. Institutions coefficient = 0.52. A positive relationship — but reverse causation and omitted variables make it impossible to interpret as causal.
  2. Switch to First stage. Settler mortality strongly predicts modern institutions with F = 16.3 (> 10 ✓). The instrument is relevant — historical mortality carved out today's institutions.
  3. Switch to IV / 2SLS. Institutions coefficient = 0.94 — nearly twice OLS. OLS was biased downward (attenuation from measurement error in institutions quality).
  4. Translate 0.94 back to levels. e0.94 ≈ 2.56. A 1-unit improvement in institutions is associated with roughly a 156% rise in GDP per capita — a massive structural effect, once you purge endogeneity.

Take-away: A good instrument turns an observational study into something close to an experiment — when theory picks the right historical lever, IV recovers the causal story OLS cannot. Read §13.8 in the chapter →

Python Libraries and Code

You've explored the key concepts interactively — now reproduce them in Python. This self-contained code block covers everything you practiced above. Copy it into an empty notebook and run it.

# =============================================================================
# CHAPTER 13 CHEAT SHEET: Case Studies for Multiple Regression
# =============================================================================

# --- Libraries ---
import numpy as np                        # numerical operations and log transforms
import pandas as pd                       # data loading and manipulation
import matplotlib.pyplot as plt           # creating plots and visualizations
from statsmodels.formula.api import ols   # OLS regression with R-style formulas
from scipy import stats                   # F-distribution for hypothesis tests

# --- Data source ---
URL = "https://raw.githubusercontent.com/quarcs-lab/data-open/master/AED/"

# =============================================================================
# STEP 1: Cobb-Douglas Production Function — log-linearize and estimate
# =============================================================================
# Taking logs converts Q = A*K^alpha*L^beta into a linear model estimable by OLS
data_cobb = pd.read_stata(URL + "AED_COBBDOUGLAS.DTA")
data_cobb['lnq'] = np.log(data_cobb['q'])
data_cobb['lnk'] = np.log(data_cobb['k'])
data_cobb['lnl'] = np.log(data_cobb['l'])

# OLS with HAC standard errors (time series: autocorrelation + heteroskedasticity)
model_cobb = ols('lnq ~ lnk + lnl', data=data_cobb).fit(
    cov_type='HAC', cov_kwds={'maxlags': 3}
)

alpha = model_cobb.params['lnk']   # capital elasticity
beta  = model_cobb.params['lnl']   # labor elasticity

print(f"Capital elasticity α = {alpha:.3f}, Labor elasticity β = {beta:.3f}")
print(f"Sum α + β = {alpha + beta:.3f}  (≈1 → constant returns to scale)")
print(f"R² = {model_cobb.rsquared:.4f}")
model_cobb.summary()

# F-test for constant returns to scale: H0: α + β = 1
data_cobb['lnq_l'] = data_cobb['lnq'] - data_cobb['lnl']
data_cobb['lnk_l'] = data_cobb['lnk'] - data_cobb['lnl']
model_r = ols('lnq_l ~ lnk_l', data=data_cobb).fit()
f_stat = ((model_r.ssr - model_cobb.ssr) / 1) / (model_cobb.ssr / model_cobb.df_resid)
p_val  = 1 - stats.f.cdf(f_stat, 1, model_cobb.df_resid)
print(f"CRS test: F = {f_stat:.2f}, p = {p_val:.3f} → {'Fail to reject' if p_val > 0.05 else 'Reject'} CRS")

# =============================================================================
# STEP 2: Phillips Curve — omitted variables bias reverses the sign
# =============================================================================
# Pre-1970 the trade-off works; post-1970 it breaks because expected inflation
# is omitted — a textbook demonstration of OVB
data_phil = pd.read_stata(URL + "AED_PHILLIPS.DTA")

# Pre-1970: classic negative relationship
pre = data_phil[data_phil['year'] < 1970]
m_pre = ols('inflgdp ~ urate', data=pre).fit(cov_type='HAC', cov_kwds={'maxlags': 3})
print(f"\nPre-1970 slope: {m_pre.params['urate']:.3f}  (negative → classic Phillips curve)")

# Post-1970: sign flips due to omitted expected inflation
post = data_phil[data_phil['year'] >= 1970]
m_post = ols('inflgdp ~ urate', data=post).fit(cov_type='HAC', cov_kwds={'maxlags': 5})
print(f"Post-1970 slope: {m_post.params['urate']:.3f}  (positive → breakdown!)")

# Augmented model: adding expected inflation restores the negative sign
post_exp = post.dropna(subset=['inflgdp1yr'])
m_aug = ols('inflgdp ~ urate + inflgdp1yr', data=post_exp).fit(
    cov_type='HAC', cov_kwds={'maxlags': 5}
)
print(f"Augmented slope on urate: {m_aug.params['urate']:.3f}  (negative again!)")
print(f"Expected inflation coef:  {m_aug.params['inflgdp1yr']:.3f}")

# OVB formula: E[b2] = β2 + β3*γ
m_aux = ols('inflgdp1yr ~ urate', data=post_exp).fit()
predicted = m_aug.params['urate'] + m_aug.params['inflgdp1yr'] * m_aux.params['urate']
print(f"OVB predicted bivariate slope: {predicted:.3f}  (actual: {m_post.params['urate']:.3f})")

# =============================================================================
# STEP 3: RAND Health Insurance Experiment — RCT as the gold standard
# =============================================================================
# Random assignment to insurance plans eliminates selection bias
data_rand = pd.read_stata(URL + "AED_HEALTHINSEXP.DTA")
data_rand_y1 = data_rand[data_rand['year'] == 1]

# Mean spending by plan
print(f"\n{'Plan':<12} {'Mean $':>10} {'N':>8}")
for plan, grp in data_rand_y1.groupby('plan')['spending']:
    print(f"{plan:<12} {grp.mean():>10,.0f} {len(grp):>8,}")

# Regression with cluster-robust SEs by family
model_rct = ols('spending ~ coins25 + coins50 + coins95 + coinsmixed + coinsindiv',
                data=data_rand_y1).fit(
    cov_type='cluster', cov_kwds={'groups': data_rand_y1['idfamily']}
)
model_rct.summary()

# Joint F-test: do insurance plans matter?
ftest = model_rct.f_test('coins25 = coins50 = coins95 = coinsmixed = coinsindiv = 0')
print(f"Joint F-test: F = {ftest.fvalue:.2f}, p = {ftest.pvalue:.4f}")

# =============================================================================
# STEP 4: Difference-in-Differences — health clinic access in South Africa
# =============================================================================
# DiD removes both time-invariant confounders and common time trends
data_did = pd.read_stata(URL + "AED_HEALTHACCESS.DTA")

# Manual DiD calculation
pre_c  = data_did[(data_did['hightreat']==0) & (data_did['post']==0)]['waz'].mean()
post_c = data_did[(data_did['hightreat']==0) & (data_did['post']==1)]['waz'].mean()
pre_t  = data_did[(data_did['hightreat']==1) & (data_did['post']==0)]['waz'].mean()
post_t = data_did[(data_did['hightreat']==1) & (data_did['post']==1)]['waz'].mean()
did    = (post_t - pre_t) - (post_c - pre_c)
print(f"\nDiD estimate (manual): {did:.3f} SD improvement in child nutrition")

# DiD regression with cluster-robust SEs by community
model_did = ols('waz ~ hightreat + post + postXhigh', data=data_did).fit(
    cov_type='cluster', cov_kwds={'groups': data_did['idcommunity']}
)
print(f"DiD coefficient (regression): {model_did.params['postXhigh']:.3f}")
print(f"p-value: {model_did.pvalues['postXhigh']:.4f}")

# =============================================================================
# STEP 5: Regression Discontinuity — incumbency advantage in U.S. Senate
# =============================================================================
# Candidates who barely win vs barely lose are quasi-randomly assigned to
# incumbent status — the jump at margin = 0 is the causal effect
data_rd = pd.read_stata(URL + "AED_INCUMBENCY.DTA")
data_rd = data_rd[data_rd['vote'].notna()].copy()

model_rd = ols('vote ~ win + margin', data=data_rd).fit(cov_type='HC1')
print(f"\nIncumbency advantage: {model_rd.params['win']:.1f} percentage points")
print(f"95% CI: [{model_rd.conf_int().loc['win', 0]:.1f}, {model_rd.conf_int().loc['win', 1]:.1f}]")
print(f"p-value: {model_rd.pvalues['win']:.4f}")

# =============================================================================
# STEP 6: Instrumental Variables — do institutions cause growth?
# =============================================================================
# OLS is biased by reverse causation; settler mortality instruments for
# modern institutions (relevant + exogenous to modern GDP)
data_iv = pd.read_stata(URL + "AED_INSTITUTIONS.DTA")

# OLS (biased)
m_ols = ols('logpgp95 ~ avexpr', data=data_iv).fit(cov_type='HC1')

# First stage: institutions ~ settler mortality
m_1st = ols('avexpr ~ logem4', data=data_iv).fit(cov_type='HC1')
print(f"\nFirst-stage F = {m_1st.fvalue:.1f}  ({'Strong' if m_1st.fvalue > 10 else 'Weak'} instrument)")

# Second stage: GDP ~ predicted institutions
data_iv['avexpr_hat'] = m_1st.fittedvalues
m_2nd = ols('logpgp95 ~ avexpr_hat', data=data_iv).fit(cov_type='HC1')

print(f"OLS coefficient:  {m_ols.params['avexpr']:.3f}  (biased)")
print(f"IV/2SLS coefficient: {m_2nd.params['avexpr_hat']:.3f}  (causal)")
print(f"Causal effect: 1-unit improvement in institutions → "
      f"{np.exp(m_2nd.params['avexpr_hat']):.1f}x increase in GDP")
Open empty Colab notebook →