A health economist, a portfolio manager and a macroeconomist seem to work in separate worlds. Yet each reaches for the same instrument:
\[y_i = \beta_1 + \beta_2\, x_i + u_i\]
The hard part is not running the regression. It is reading the slope honestly: in the right units, with the right standard errors, against the right hypothesis, and with an eye on the few observations that can drive it.
One estimator, four case files. In each, we ask what the slope means, how sure we are, and what could make it misleading.
We expect \(\beta_2 > 0\) for life expectancy and \(\beta_2 < 0\) for infant mortality. Spending is chosen by countries, not assigned, so expect associations.
The data: spending varies almost ninefold across the OECD
Variable
Mean
Std. dev.
Min
Max
hlthpc (US$)
3,255.6
1,493.7
923 (Mexico)
7,990 (USA)
lifeexp (years)
76.70
2.94
69.8 (Estonia)
79.9 (Switzerland)
infmort (per 1,000)
4.45
2.72
1.8 (Iceland)
14.7 (Mexico)
N = 34 · OECD, 2009 · Table 8.1 of the chapter
The USA spends $2,642 more per person than the next-highest country, Norway. That alone makes it a candidate for an influential observation.
Code: load the data and summarize it
!pip install -q pyfixest # pyfixest is not preinstalled in Colabimport pandas as pd # tabular dataimport pyfixest as pf # OLS with R-style formulas and robust SEsurl ="https://raw.githubusercontent.com/quarcs-lab/data-open/master/AED/AED_HEALTH2009.DTA"data_health = pd.read_stata(url) # 34 OECD countries in 2009, one row eachtable81 = data_health[["hlthpc", "lifeexp", "infmort"]].describe().T # one row per variabletable81["range"] = table81["max"] - table81["min"] # max minus minprint(table81[["mean", "std", "min", "max", "range"]].round(2)) # Table 8.1
Fitting twice costs nothing: vcov changes the standard errors, never the line.
Output: +1.11 years per $1,000, but the robust SE is 1.6× larger
Variable
Coef.
SE (iid)
\(t\)
\(p\)
SE (HC1)
\(t\)
\(p\)
Intercept
73.084
1.024
71.36
< 0.001
1.565
46.70
< 0.001
hlthpc
0.001112
0.000287
3.88
< 0.001
0.000464
2.40
0.023
N = 34 · \(R^2 = 0.320\) · 95% CI for the slope: [0.000528, 0.001696] (iid), [0.000167, 0.002056] (HC1)
Units: 0.001112 years per dollar = 1.11 years per $1,000 of spending per person
Robust 95% CI per $1,000: from 0.17 to 2.06 years, positive but wide
Intercept 73.1: predicted life expectancy at zero spending, far below the lowest observed spending ($923), so it has no meaning on its own
Significant either way (\(p\) = 0.023 with HC1), but the robust interval is 1.6× wider. Health spending explains 32% of the cross-country variation.
Reading the slope: does the line predict real gaps?
Chile spends $1,210 per person; Norway spends $5,348, a gap of $4,138
The slope predicts a life-expectancy gap of \(4.138 \times 1.11 \approx\)4.6 years
The actual gap is 78.7 − 75.8 = 2.9 years
The line is an average relationship across 34 countries; any one pair can be far from it
A slope summarizes the cloud, not every pair inside it. A typical prediction error is about 2.4 years (the RMSE in the output).
The USA lives 6 years shorter than its spending predicts
Male life expectancy against health spending, with the OLS line. The USA sits far below the line at the far right.
At $7,990 the line predicts 82.0 years; the USA records 76.0. Its residual of −6.0 years is the largest of all 34 countries.
Why the robust SE is larger: big residuals at extreme spending
Residuals against health spending. The largest residuals sit at the lowest (Mexico, Turkey) and highest (USA) spending.
HC1 weights each squared residual by \((x_i-\bar x)^2\). The USA is both the most extreme spender and the worst-fitting country, so it inflates the robust SE.
Code: infant mortality on health spending
# continues from the previous blockmodel_infmort = pf.feols("infmort ~ hlthpc", data=data_health) # classical SEsmodel_infmort_robust = pf.feols("infmort ~ hlthpc", data=data_health, vcov="HC1") # robust SEsmodel_infmort.summary() # slope, classical SE, t, pmodel_infmort_robust.summary() # same slope, HC1 SE, t, pslope_inf = model_infmort.coef()["hlthpc"] # infant deaths per 1,000 births per extra dollarprint(f"per $1,000: {slope_inf*1000:.2f}; from $2,000 to $4,000: {slope_inf*2000:.2f}")
Output: the verdict flips with robust standard errors
Variable
Coef.
SE (iid)
\(t\)
\(p\)
SE (HC1)
\(t\)
\(p\)
Intercept
6.702
1.064
6.30
< 0.001
1.877
3.57
0.001
hlthpc
−0.000693
0.000298
−2.33
0.027
0.000515
−1.35
0.188
N = 34 · \(R^2 = 0.145\) · 95% CI for the slope: [−0.001299, −0.000086] (iid), [−0.001741, 0.000356] (HC1)
Units: −0.69 infant deaths per 1,000 births for every extra $1,000 per person
Classical SEs: significant at 5% (\(t = -2.33\))
HC1: the SE is 1.7× larger, \(t = -1.35\), \(p = 0.19\), and the CI includes zero
Same slope, opposite verdicts. With robust SEs, the data cannot rule out a zero association.
The USA again: 6.4 infant deaths where the line predicts 1.2
Infant mortality against health spending. The line’s prediction for the USA falls below the lowest rate observed anywhere in the sample.
The prediction for the USA (1.2) is lower than any country actually achieves (Iceland, 1.8). A straight line extrapolated to $7,990 promises the impossible.
Interpreting the infant-mortality slope: magnitude vs evidence
Economic magnitude (the chapter’s example): going from $2,000 to $4,000 per person predicts 1.39 fewer deaths per 1,000 births, i.e. about 139 fewer infant deaths a year in a country with 100,000 births
Statistical evidence: with robust SEs we cannot distinguish that slope from zero (\(p = 0.19\))
Fit: spending explains only 14.5% of the variation; Mexico and Turkey’s high rates dominate the cloud
Economically large if true, statistically weak in this sample. Report both, and do not let a big number stand in for evidence.
Robustness check: the same regressions without the USA
Outcome
Sample
Slope per $1,000
HC1 \(t\)
\(p\)
\(R^2\)
Life expectancy
all 34
1.11
2.40
0.023
0.320
Life expectancy
without USA (33)
1.69
5.60
< 0.001
0.507
Infant mortality
all 34
−0.69
−1.35
0.188
0.145
Infant mortality
without USA (33)
−1.20
−2.46
0.019
0.302
Not in the chapter: the same pf.feols model with vcov="HC1" on 33 countries, mirroring the chapter’s own outlier check in 8.2.
One country flattens both slopes and hides the infant-mortality association. Dropping it is not automatically right, but you must know it matters.
Code: the USA prediction and the drop-one check
# continues from the previous blockresid = data_health["lifeexp"] - model_lifeexp.predict() # actual minus fitted, by countryprint(data_health.assign(resid=resid) .nsmallest(3, "resid")[["code", "lifeexp", "resid"]]) # USA has the largest shortfallus = data_health[data_health["code"] =="USA"] # the United States rowprint(model_lifeexp.predict(newdata=us), # life expectancy at $7,990 model_infmort.predict(newdata=us)) # infant mortality at $7,990no_us = data_health[data_health["code"] !="USA"] # not in the chapter: 33 countriesfor y in ["lifeexp", "infmort"]: # same model for both outcomes fit = pf.feols(f"{y} ~ hlthpc", data=no_us, vcov="HC1") # robust SEs, as beforeprint(y, round(fit.coef()["hlthpc"] *1000, 2), # slope per $1,000round(fit.tstat()["hlthpc"], 2), round(fit._r2, 3)) # robust t and R²
Output: without two countries the slope rises 41% and \(R^2\) reaches 0.93
Variable
Coef.
SE (iid)
\(t\)
\(p\)
SE (HC1)
\(t\)
\(p\)
Intercept
−883.3
208.9
−4.23
< 0.001
213.4
−4.14
< 0.001
gdppc
0.1267
0.0064
19.69
< 0.001
0.0076
16.65
< 0.001
N = 32 (USA and Luxembourg excluded) · \(R^2 = 0.928\) · 95% CI for the slope: [0.1136, 0.1399] (iid), [0.1112, 0.1423] (HC1)
Slope: $90 → $127 per $1,000 of GDP (+41%)
\(R^2\): 0.604 → 0.928; the robust SE is now only 1.2× the classical SE
Elasticity at the subset means: \(0.1267 \times 31{,}117 / 3{,}060 \approx\)1.29
Remove two countries and the slope, the fit, the SE gap and the economic story (necessity → luxury) all change.
Two lines, two stories
Health spending against GDP per capita. Purple: OLS on all 34 countries. Yellow dashed: OLS on the 32 countries without the USA and Luxembourg.
The USA sits far above any line; Luxembourg sits far to the right and below. Together they tilt the full-sample line and loosen the fit.
Anatomy of influence: drop each country on its own
Sample
N
Slope
HC1 \(t\)
\(R^2\)
What changes
All countries
34
0.0899
3.08
0.604
—
Without USA
33
0.0814
3.37
0.701
fit improves, slope barely moves
Without Luxembourg
33
0.1413
8.33
0.826
slope jumps by 57%
Without both
32
0.1267
16.65
0.928
both effects
Not in the chapter: the same HC1 regression on each subsample.
The USA is an outlier in \(y\): a huge residual that mostly adds noise. Luxembourg is a high-leverage point in \(x\) that drags the slope down.
Reading the robustness check correctly
\(R^2 = 0.60\) is not a “weak fit” for a cross-section of 34 countries; GDP explains most of the variation
Removing the two worst-fitting points mechanically raises \(R^2\): you deleted the largest residuals
The subset line describes “the other 32 countries”, not a “true” relationship that the outliers were hiding
Legitimate reasons to exclude: a data error, or a unit from a different population (a tiny financial centre whose GDP counts cross-border commuters’ output)
The honest report shows both fits and says why they differ
Outlier checks are for understanding a slope, not for choosing the sample that gives the nicest \(R^2\).
Caveats for 8.2
Association, not causation: GDP and health spending both reflect institutions, demographics and prices
One year (2009): a recession year; GDP fell more than health budgets in many countries
Functional form: a straight line in dollars imposes a varying elasticity; logs (Chapter 9) are the standard alternative
Small N: with 34 observations, two countries move every statistic, as we just saw
Health spending rises with income, at roughly 9–13 cents per dollar depending on two countries. Everything finer than that needs more data or more structure.
Case 8.3 · How risky is Coca-Cola stock?
Financial time series · CAPM beta · testing \(\beta = 1\) and \(\alpha = 0\)
Risk split:\(R^2 = 0.201\), so the market accounts for 20% of the variance of Coca-Cola’s excess return; 80% is idiosyncratic
Coca-Cola is more volatile than the market, but most of that volatility is its own. The part that moves with the market is only 61% as large as the market’s.
Code: test \(\beta = 1\) and annualize alpha
# continues from the previous blockb = model_capm_robust.coef()["rm_rf"] # estimated betase = model_capm_robust.se()["rm_rf"] # robust standard error of betat_beta1 = (b -1) / se # H0: beta = 1 (as risky as the market)df = model_capm._N -2# degrees of freedom, n − 2print("t =", t_beta1, " p =", 2* stats.t.sf(abs(t_beta1), df)) # two-sided p-valuealpha = model_capm.coef()["Intercept"] # monthly alpha, in decimalsprint("alpha x 12 =", 12* alpha) # simple annualization (the chapter's 8.2%)print("compounded =", (1+ alpha) **12-1) # compounding twelve monthly returns
The data say clearly that Coca-Cola is a defensive stock. They also say it earned more than its beta justified, which the CAPM says should not happen.
80% of Coca-Cola’s month-to-month risk is its own
Coca-Cola’s excess return against the market’s, 354 months. Purple: OLS line (β = 0.61). Yellow dashed: what β = 1 and α = 0 would look like.
The fitted line is flatter than the β = 1 reference and crosses zero slightly above the origin. The wide vertical scatter is idiosyncratic risk, about 80% of the variance.
Why is alpha positive? Three readings
The model is incomplete: other priced risk factors (size, value, momentum) are missing, so “alpha” is compensation for risks the CAPM ignores
Genuine outperformance: Coca-Cola beat its risk-adjusted benchmark over 1983–2012
Selection after the fact: Coca-Cola is famous because it did well. Studying a known winner over its winning period guarantees a positive alpha
One significant alpha in one hand-picked stock is weak evidence against the CAPM. Tests of the CAPM use many assets at once.
Code: the same regression for other stocks and other periods
# continues from the previous block — not in the chapter: simple comparisons, same estimatorfor y in ["rko_rf", "rwmt_rf", "rtgt_rf"]: # Coca-Cola, Walmart, Target fit = pf.feols(f"{y} ~ rm_rf", data=data_capm, vcov="HC1") # CAPM for each stockprint(y, fit.coef().round(4).to_dict(), round(fit._r2, 3)) # alpha, beta, R²half =len(data_capm) //2# split the 354 months in twofor name, part in [("1983–1998", data_capm.iloc[:half]), ("1998–2012", data_capm.iloc[half:])]: fit = pf.feols("rko_rf ~ rm_rf", data=part, vcov="HC1") # Coca-Cola, each halfprint(name, fit.coef().round(4).to_dict(), fit.pvalue().round(3).to_dict())
Output: betas differ across stocks and drift over time
Regression (HC1 SEs)
Months
\(\hat\alpha\)
\(p(\alpha)\)
\(\hat\beta\)
SE(\(\beta\))
\(R^2\)
Coca-Cola, full sample
354
0.0068
0.019
0.606
0.077
0.201
Walmart, full sample
354
0.0080
0.017
0.748
0.091
0.236
Target, full sample
354
0.0044
0.240
1.067
0.116
0.333
Coca-Cola, 1983–1998
177
0.0112
0.002
0.844
0.095
0.344
Coca-Cola, 1998–2012
177
0.0011
0.800
0.437
0.113
0.117
Not in the chapter: the same HC1 CAPM regression on other columns of AED_CAPM.DTA and on two halves of the sample (May 1983 – Jan 1998, Feb 1998 – Oct 2012).
Target is about as risky as the market; the two staples are defensive. Coca-Cola’s beta halved over time, and its alpha came from the first half.
Caveats for 8.3
Standard errors: for monthly returns HC1 is reasonable: the residuals show almost no autocorrelation (lag-1 correlation −0.04)
The CAPM itself does not assume returns are independent over time; the classical OLS standard errors do. That is a statement about inference, not about the model
Time-varying beta: one beta for 30 years averages over very different periods (0.84 vs 0.44)
Fat tails: crash months such as October 1987 carry a lot of weight in a least-squares fit
Market proxy: a value-weighted index stands in for “the market portfolio” the theory refers to
\(\hat\beta = 0.61\) is a well-measured average sensitivity over 1983–2012. Use it to describe the past, not as a constant of nature.
Case 8.4 · Does Okun’s Law hold for the U.S.?
Macro time series · testing \(\beta_2 = -2\) · structural breaks
The question: how much growth do we lose when unemployment rises?
N = 59 · \(R^2 = 0.592\) · 95% CI for the slope: [−1.939, −1.239] (iid), [−1.915, −1.263] (HC1)
Slope: a year in which unemployment rises by 1 percentage point has growth about 1.59 percentage points lower
Intercept 3.0: predicted growth in a year when unemployment is unchanged, roughly the economy’s trend growth
HC1 SE is slightly smaller than the classical SE (0.93×): robust is not always bigger
A strong, tight association: unemployment changes alone account for 59% of the year-to-year variation in growth.
Reading the coefficients in percentage points
Unemployment 5% → 6% (+1 pp): predicted growth falls from 3.0% to \(3.008 - 1.589 \approx\)1.4%
Break-even: unemployment is predicted to stay flat only when growth is about 3.0%
Zero growth goes with a predicted rise in unemployment of \(3.008 / 1.589 \approx\)1.9 pp
Percent vs percentage point: growth is a percent change; the regressor is a change in a rate, so its unit is the percentage point
Okun’s Law is a statement about percentage points. “Unemployment up 1%” would mean from 5% to 5.05%, a completely different claim.
Code: test the −2 benchmark and inspect the residuals
# continues from the previous blockfrom scipy import stats # t distributionb = model_okun.coef()["uratechange"] # slope estimatese = model_okun.se()["uratechange"] # classical SE, as in the chaptert_okun = (b - (-2.0)) / se # H0: beta2 = -2print("t =", t_okun, " p =", 2* stats.t.sf(abs(t_okun), model_okun._N -2))pred = model_okun.predict() # fitted growth, year by yearresid = data_gdp["rgdpgrowth"] - pred # actual minus predictedlate = (data_gdp["year"] >=2010).values # the 2010sprint("2010s below prediction:", (resid[late] <0).sum(), "of", late.sum())print("mean actual vs predicted:", data_gdp["rgdpgrowth"][late].mean(), pred[late].mean())
Output: −2 is rejected at 5%, though the estimate is not far from it
Test of \(H_0: \beta_2 = -2\)
Estimate
SE
\(t\)
\(p\)
95% CI
Classical SEs
−1.589
0.175
2.35
0.022
[−1.939, −1.239]
HC1 SEs
−1.589
0.163
2.52
0.014
[−1.915, −1.263]
Both CIs exclude −2, but only just: −2 lies 0.06–0.09 below their lower ends
At the 1% level we would not reject −2 (\(p > 0.01\) either way)
Economically, −1.59 vs −2 is a difference of 0.4 points of growth per point of unemployment
Statistically different from −2 at 5%, economically close. The U.S. relationship in 1961–2019 is somewhat weaker than the rule of thumb.
Every year of negative growth had rising unemployment
GDP growth against the change in unemployment, one point per year. Purple: OLS line (−1.59). Yellow dashed: a line with slope −2 drawn through the sample means.
Recessions sit in the lower right: unemployment up, growth down. All 7 years with negative growth (1974, 1975, 1980, 1982, 1991, 2008, 2009) had unemployment rising.
Actual vs predicted: the line works until the 2010s
Actual real GDP growth and the growth predicted from unemployment changes, 1961–2019. Shaded: the gap in 2010–2019.
From 2010 to 2019 actual growth fell below the prediction in 9 of 10 years, by 1.6 points on average (2.3% actual vs 3.9% predicted).
Was the fit “excellent” before 2008?
Period
Years
Root-mean-squared error
Mean residual
1961–2007
47
1.16
+0.37
2008–2019
12
1.70
—
2010–2019
10
—
−1.62
Computed from the chapter’s fitted model; no re-estimation.
Before 2008 the typical miss was still about 1.2 points of growth, e.g. 1974: −0.5% actual vs +1.9% predicted
After 2008 the misses are larger and one-signed, the signature of a shift in the relationship
The relationship was good, not excellent, before 2008, and it broke down in a systematic way afterwards. One line for 59 years hides that.
Caveats for 8.4: time series need time-series inference
Autocorrelation: the residuals’ lag-1 correlation is 0.47; good and bad years come in runs
HC1 corrects for heteroskedasticity but not for autocorrelation; the textbook fix is HAC (Newey–West) SEs, used in Chapters 13 and 17
Structural break: one slope for 1961–2019 averages over at least two regimes
Not causal: both growth and unemployment respond to the same demand and supply shocks; Okun’s Law is a regularity, not a lever
With autocorrelated errors, both the classical and HC1 SEs are likely too small, so the rejection of −2 at 5% is less secure than it looks.
Reading it right
Act III · The resolution
Four cases, side by side
Case
Slope, in its units
\(R^2\)
SE ratio, HC1 / classical
Does HC1 change the verdict?
Main threat
8.1 life expectancy
+1.11 years per $1,000
0.32
1.62
no (\(p\) 0.023)
USA; income confounding
8.1 infant mortality
−0.69 deaths per 1,000 per $1,000
0.14
1.73
yes: \(p\) 0.027 → 0.188
USA, Mexico, Turkey
8.2 health spending
+$90 per $1,000 of GDP
0.60
2.27
no (\(p\) 0.004)
USA, Luxembourg
8.3 CAPM
\(\beta = 0.61\) (unit-free)
0.20
1.19
no
time-varying beta
8.4 Okun
−1.59 pp per pp
0.59
0.93
no
autocorrelation, break
Robust SEs matter most in small cross-sections with high-leverage countries, least in the long time series.
Testing the hypothesis that matters, not just \(\beta = 0\)
Case
Null hypothesis
Why it matters
Robust \(t\)
Verdict at 5%
8.1–8.2
\(\beta_2 = 0\)
is there any association?
2.40, −1.35, 3.08
reject, not reject, reject
8.3
\(\beta = 1\)
is the stock as risky as the market?
−5.11
reject: defensive
8.3
\(\alpha = 0\)
does the CAPM price this stock?
2.35
reject (but see selection)
8.4
\(\beta_2 = -2\)
does the rule of thumb hold?
2.52
reject, narrowly
Theory supplies the interesting null. The \(t\)-statistic is the same formula with a different number subtracted.
One country, two case studies: what the USA does to the slopes
Regression
All countries
Without the USA
Change
Life expectancy on spending, per $1,000
1.11
1.69
slope +52%, \(R^2\) 0.32 → 0.51
Infant mortality on spending, per $1,000
−0.69
−1.20
becomes significant with HC1 (\(p\) 0.019)
Health spending on GDP, per $1,000
$89.9
$81.4
\(R^2\) 0.60 → 0.70
The USA spends the most and gets less than the line predicts. Whether it belongs in the sample is a question about the population you want to describe.
The tempting mistake — and the correction
Objection. “The USA and Luxembourg are outliers. Drop them and the true relationship appears: \(R^2 = 0.93\).”
Response. Deleting the two largest residuals raises \(R^2\) by construction. The 32-country line describes the other 32 countries; it is not truer, it answers a narrower question. The USA is a real health system, and its distance from the line is the most policy-relevant fact in the dataset.
Use outlier checks to learn which observations drive a result and why, then report every version you looked at.
Five questions to ask of any bivariate slope
Units: what does one unit of \(x\) and of \(y\) mean, and how big is the slope in a meaningful unit?
Uncertainty: classical or robust? Does the verdict change? For time series, is HAC needed?
Influence: which observations sit far from \(\bar x\) or far from the line, and what happens without them?
Hypothesis: is \(\beta = 0\) the interesting null, or does theory supply a better one, such as \(\beta = 1\) or \(\beta = -2\)?
Interpretation: is this an association, or does something in the design justify a causal reading?
Each case in this chapter failed at least one question on the first pass. Asking all five is the craft.
What to carry forward
One estimator, pf.feols, fits cross-sectional, financial and macro data alike
Add vcov="HC1" for cross-sections; for time series, look ahead to HAC standard errors
Translate every slope into meaningful units before judging its size
Find the influential observations, refit without them, and report both
Test the hypothesis theory gives you, and keep economic and statistical significance apart
Next: Chapter 9 puts the variables in logs, so slopes become elasticities and percentage effects directly.
Fit the line in any domain — then read the slope in its units, with honest standard errors and an eye on influence.